Android Firebase cannot download images and add them to a Bitmap Array - firebase

I am currently working on a matching card game where I need to store the images on Firebase. I am uploading the images by a button click when I start the game(doing it automatically creates same problem but the button one is safer) I think the Image isn't getting downloaded fast enough to show on the card face or it might not be working in a sequence with the whole app so the bitmap array gets zero elements inside. My current code is:
class game2x2 : AppCompatActivity() {
private lateinit var database: DatabaseReference
private lateinit var buttons: List<ImageButton>
//private lateinit var bitmapArray: ArrayList<Bitmap>
private var bitmapArray = mutableListOf<Bitmap>()
private lateinit var button1: ImageButton
private lateinit var button2: ImageButton
private lateinit var button3: ImageButton
private lateinit var button4: ImageButton
private lateinit var upload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game2x2)
val min = 1
val max = 45
val database = FirebaseDatabase.getInstance()
val imageID1 = Random().nextInt(max - min + 1) + min
val imageID2 = Random().nextInt(max - min + 1) + min
val aDatabase = FirebaseStorage.getInstance().getReference("all/$imageID1.jpg")
val sDatabase = FirebaseStorage.getInstance().getReference("all/$imageID2.jpg")
upload = findViewById(R.id.uploadButton)
button1 = findViewById(R.id.imageButton1)
button2 = findViewById(R.id.imageButton2)
button3 = findViewById(R.id.imageButton3)
button4 = findViewById(R.id.imageButton4)
buttons = listOf(button1, button2, button3, button4)
upload.setOnClickListener(View.OnClickListener {
try {
val localfile = File.createTempFile("tempfile", ".jpg")
aDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
val localfile = File.createTempFile("tempfile1", ".jpg")
sDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
/// DUPLICATE
bitmapArray.addAll(bitmapArray)
///SHUFFLE
bitmapArray.shuffle()
Log.w("myapplication", bitmapArray.size.toString())
})
buttons.forEachIndexed { index, button ->
button.setOnClickListener(View.OnClickListener {
button.setImageBitmap(bitmapArray[index])
})
}
}
}
Is there any other way to retrieve image from the Firebase Storage besides downloading and adding it to a temporary file and then decoding it to a bitmap?
I tried anything that I could find. I even tried adding the access tokens of the images to a realtime database and then getting them from there but I failed terribly. Thanks in advance for helping!

Since getFile() an asynchronous task I would imagine your log statement Log.w("myapplication", bitmapArray.size.toString()) is executing while the bitmapArray is still empty? This would happen because the aDatabase.getFile().addOnSuccessListener {} and sDatabase.getFile().addOnSuccessListener {} won't execute until the download finishes, but allow the rest of your function to continue to execute.
What you need to do is await the results of the downloads before continuing with the duplicate and shuffle portions.
getFile() returns a FileDownloadTask, which inherits from StorageTask. StorageTask has an isComplete() method -- and a few others the may be useful for errors cases. One option would be to capture the FileDownloadTask in a variable and not continue executing until your downloads are finished. However, be warned this might freeze up your main thread.
Edit: Instead of checking status on the main thread, you might want to try something like disabling the buttons until the images are ready. See edit comments:
class game2x2 : AppCompatActivity() {
private lateinit var database: DatabaseReference
private lateinit var buttons: List<ImageButton>
//private lateinit var bitmapArray: ArrayList<Bitmap>
private var bitmapArray = mutableListOf<Bitmap>()
private lateinit var button1: ImageButton
private lateinit var button2: ImageButton
private lateinit var button3: ImageButton
private lateinit var button4: ImageButton
private val numImages = 2 // EDIT total number of images we need to download
private val numImagesReady = AtomicInteger(0) // EDIT count of how many images are currently ready
private lateinit var upload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game2x2)
val min = 1
val max = 45
val database = FirebaseDatabase.getInstance()
val imageID1 = Random().nextInt(max - min + 1) + min
val imageID2 = Random().nextInt(max - min + 1) + min
val aDatabase = FirebaseStorage.getInstance().getReference("all/$imageID1.jpg")
val sDatabase = FirebaseStorage.getInstance().getReference("all/$imageID2.jpg")
upload = findViewById(R.id.uploadButton)
button1 = findViewById(R.id.imageButton1)
button2 = findViewById(R.id.imageButton2)
button3 = findViewById(R.id.imageButton3)
button4 = findViewById(R.id.imageButton4)
buttons = listOf(button1, button2, button3, button4)
// EDIT disable buttons until all images are ready
buttons.forEach {
it.setEnabled(false)
}
upload.setOnClickListener(View.OnClickListener {
try {
val localfile = File.createTempFile("tempfile", ".jpg")
aDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
// EDIT add the image twice here instead of duplicating later
bitmapArray.add(bitmap)
// EDIT count this image as ready
val totalImagesReady = numImagesReady.incrementAndGet()
// EDIT once all images are ready, shuffle and enable the buttons
if (totalImagesReady == numImages) {
bitmapArray.shuffle()
buttons.forEach { it.setEnabled(true) }
}
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
// SUGGESTION especially if this will be implemented 8x8, you might want to try implementing this in a loop instead of duplicating code
val localfile = File.createTempFile("tempfile1", ".jpg")
sDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
// EDIT add the image twice here instead of duplicating later
bitmapArray.add(bitmap)
// EDIT count this image as ready
val totalImagesReady = numImagesReady.incrementAndGet()
// EDIT once all images are ready, shuffle and enable the buttons
if (totalImagesReady == numImages) {
bitmapArray.shuffle()
buttons.forEach { it.setEnabled(true) }
}
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
// EDIT moved /// DUPLICATE
// EDIT refactor bitmapArray.addAll(bitmapArray)
// EDIT moved ///SHUFFLE
// EDIT moved bitmapArray.shuffle()
// EDIT remove Log.w("myapplication", bitmapArray.size.toString())
})
buttons.forEachIndexed { index, button ->
button.setOnClickListener(View.OnClickListener {
button.setImageBitmap(bitmapArray[index])
})
}
}
}

Related

Download Multiple images from Firebase Realtime Database to device storage

I have a recyclerview that displays multiple images from Firebase Realtime Database. The recyclerview also has a button within it. I want this button to allow users to be able to download these images ONE AT A TIME once they click it.
Once users click "download" I want the images to be saved to their device storage. I've tried multiple solutions for this, but they weren't helpful as they were for either Firestore Database or only allowed for one image to be downloaded.
Code
class AbstractAdapter(private val mContext: Context, private val abstractList: List<Abstract>) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.download_btn.setOnClickListener {
downloadFile()
}
Glide.with(mContext)
.load(abstractList[position].abstract)
.into(holder.imageView)
}
override fun getItemCount(): Int {
return abstractList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
val download_btn: Button = itemView.findViewById(R.id.abstractDownloadBtn)
}
private fun downloadFile() {
val storage = FirebaseStorage.getInstance()
val storageRef = storage.getReferenceFromUrl("https://notes-72413.firebaseio.com/")
val islandRef = storageRef.child("Abstract")
val rootPath = File(Environment.getExternalStorageDirectory(), "abstract")
if (!rootPath.exists()) {
rootPath.mkdirs()
}
val localFile = File(rootPath, "imageName.jpeg")
islandRef.getFile(localFile)
.addOnSuccessListener(OnSuccessListener<FileDownloadTask.TaskSnapshot?> {
Log.e("firebase ", ";local tem file created created $localFile")
// updateDb(timestamp,localFile.toString(),position);
}).addOnFailureListener(OnFailureListener { exception ->
Log.e(
"firebase ",
";local tem file not created created $exception"
)
})
}
companion object {
private const val Tag = "RecyclerView"
}
I've tried this code, but once I click the "download" button it immediately crashes and Logcat says Firebase Storage URLs must point to an object in your Storage Bucket. Please obtain a URL using the Firebase Console or getDownloadUrl()
My Firebase Realtime Database
There's 64 files in total
Summary
I have a recyclerview that displays images from Firebase Realtime Database. Once users click the "download" button, it only downloads one image at a time to their device storage.
Update
private fun downloadFile() {
val storage = FirebaseStorage.getInstance()
val storageRef = storage.getReferenceFromUrl("abstract")
val rootPath = File(Environment.getExternalStorageDirectory(), "abstract")
if (!rootPath.exists()) {
rootPath.mkdirs()
}
val localFile = File(rootPath, "imageName.jpeg")
storageRef.child("Abstract").downloadUrl.addOnSuccessListener { Log.e("firebase ", ";local tem file created created $localFile")
}.addOnFailureListener(OnFailureListener { exception ->
Log.e("firebase ", ";local tem file not created created $exception")
})
}
These are the changes I made to my downloadFile function, but I still get an error:
The storage Uri could not be parsed
Second update
2022-06-11 21:36:00.536 29751-29751/com.khumomashapa.notes E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.khumomashapa.notes, PID: 29751
java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:523)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) 
Caused by: java.net.MalformedURLException: no protocol:
at java.net.URL.<init>(URL.java:601)
at java.net.URL.<init>(URL.java:498)
at java.net.URL.<init>(URL.java:447)
at com.khumomashapa.notes.adapter.AbstractAdapter.downloadFile(AbstractAdapter.kt:57)
at com.khumomashapa.notes.adapter.AbstractAdapter.onBindViewHolder$lambda-0(AbstractAdapter.kt:35)
at com.khumomashapa.notes.adapter.AbstractAdapter.$r8$lambda$Rrmx0DFlwJu1z6QtjG8WCQp6NQQ(Unknown Source:0)
at com.khumomashapa.notes.adapter.AbstractAdapter$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7216)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1217)
at android.view.View.performClickInternal(View.java:7190)
at android.view.View.access$3500(View.java:827)
at android.view.View$PerformClick.run(View.java:27663)
at android.os.Handler.handleCallback(Handler.java:900)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:219)
at android.app.ActivityThread.main(ActivityThread.java:8349)
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) 
Code
class AbstractAdapter(private val mContext: Context, private val abstractList: List<Abstract>) :
RecyclerView.Adapter<AbstractAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.abstract_image_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.download_btn.setOnClickListener {
downloadFile(url = String(), file = String.toString())
}
Glide.with(mContext)
.load(abstractList[position].abstract)
.into(holder.imageView)
}
override fun getItemCount(): Int {
return abstractList.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.abstractImageView)
val download_btn: Button = itemView.findViewById(R.id.abstractDownloadBtn)
}
#Throws(IOException::class)
private fun downloadFile(url: String, file: String) {
val urlObj = URL(url)
val fileObj = File(file)
val conn = urlObj.openConnection()
val buffer = ByteArray(1024)
object : BufferedOutputStream(FileOutputStream(fileObj)) {
var `in` = BufferedInputStream(conn.getInputStream())
init {
var read: Int
while (`in`.read(buffer, 0, buffer.size) >= 0.also { read = it });
run {
out.write(buffer, 0, read)
}
out.flush()
}
}.use { out -> }
}
I found the perfect solution to my problem. All I had to do was create an OnItemClick interface to get a different result for each item click and use Download manager to download the images.
override fun onItemClick(item: String, pos:Int) {
abstractData = item
positionItem = pos
if (checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED ){
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQ_CODE)
}else{
startDownloading()
}
Toast.makeText(requireActivity(), "Saved to Internal storage/Pictures/AbstractWallpaper", Toast.LENGTH_LONG).show()
}
private fun startDownloading() {
val request = DownloadManager.Request(Uri.parse(abstractData))
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
request.setTitle("Abstract Wallpaper")
request.setDescription("Your image is downloading")
request.allowScanningByMediaScanner()
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, "AbstractWallpapers")
val manager = activity?.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
manager.enqueue(request)
Toast.makeText(requireActivity(), "Download is starting...", Toast.LENGTH_LONG).show()
}
As you are downloading based on your storage URLs so need to use getDownloadUrl() method.
storageRef.child("Abstract").getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
...Uri
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
// To handle error
}
});
As those URLs in your database are publicly visible HTTP URLs. You can just download the files with a good old URLConnection and binary streams.
private void downloadFile(String url, String file) throws IOException {
URL urlObj = new URL(url);
File fileObj = new File(file);
URLConnection conn = urlObj.openConnection();
byte[] buffer = new byte[1024];
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileObj)) {
try (BufferedInputStream in = new BufferedInputStream(conn.getInputStream())) {
int read;
while ((read = in.read(buffer, 0, buffer.length) >= 0) {
out.write(buffer, 0, read);
}
out.flush();
}
}
}
Not tested, but for idea,
I'm using okhttp and okio
fun saveImageToPicture(context: Countext, url: String, imageName: String, imageMimeType: String) {
val appImagePath = File(Environment.DIRECTORY_PICTURES, "FolderNameInPictures")
val date = System.currentTimeMillis()
val values = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "$imageName.$imageMimeType")
put(MediaStore.Images.Media.MIME_TYPE, "image/$imageMimeType")
put(MediaStore.Images.Media.DATE_ADDED, date)
put(MediaStore.Images.Media.DATE_MODIFIED, date)
}
var collection = if (Build.VERSION.SDK_INT >= 29 {
values.put(MediaStore.Images.Media.RELATIVE_PATH, "$appImagePath${File.separator}")
values.put(MediaStore.Images.Media.IS_PENDING, 1)
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val resolver = context.contentResolver
val insertedUri = resolver.insert(collection, values)
val outputStream = resolver.openOutputStream(insertedUri!!, "w")
downloadFromUrlToOutputStream(url, outputStream)
values.clear()
if (Build.VERSION.SDK_INT >= 29 {
values.put(MediaStore.Images.Media.IS_PENDING, 0)
}
resolver.update(insertedUri, values, null, null)
}
fun downloadFromUrlToOutputStream(url: String, output: OutputStream) {
val request = Request.Builder().url(url).build()
val response = OkHttpClient().newCall(request).execute();
val body = response.body()
val bufferSource = body.source()
val sink = Okio.buffer(Okio.sink(output))
val sinkBuffer = sink.buffer()
val bufferSize = 1024 * 8
while (bufferSource.read(sinkBuffer, bufferSize) != -1) {
sink.emit()
}
sink.flush()
sink.close()
bufferSource.close()
}

The sms code from firebase does not match

I created a number sending activity and confirmation/otp fragment using firebase phone auth. When directed to the confirmation page, a 6-digit sms code from Firebase is sent to the phone number entered, but no matter what I do, the entered edittext and the codes from firebase do not match.
When I leave the edit text blank, it redirects to the fragment I want as if it were correct. Can you help me where am I making a mistake? My codes in the confirmation fragment are as follows;
class FragmentRegisterTelOnay : Fragment() {
var comingNumber = ""
lateinit var auth : FirebaseAuth
lateinit var callbacks : PhoneAuthProvider.OnVerificationStateChangedCallbacks
var verificationID = ""
var comingCode : String = ""
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {
var view = inflater.inflate(R.layout.fragment_register_activity_phone,container,false)
view.tvKullaniciTelNo.setText("+90"+comingNumber)
auth = Firebase.auth
setupCallBack()
view.ileriButton.setOnClickListener {
if (comingCode.equals(editTextOnayKodu.text.toString())){
EventBus.getDefault().postSticky(EventBusDataEvents.KayitBilgileriniGonder("+90$comingNumber",null,verificationID,comingCode))
val transaction = requireActivity().supportFragmentManager.beginTransaction()
transaction.replace(R.id.telefonOnayKod,FragmentRegisterDetailPhone())
transaction.addToBackStack("TelOnayfragmentEklendi")
transaction.commit()}
else{
Toast.makeText(activity,"Wrong Code",Toast.LENGTH_LONG).show()
}
}
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber("+90"+comingNumber) // Phone number to verify
.setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(requireActivity()) // Activity (for callback binding)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
return view
}
private fun setupCallBack() {
callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
if(!credential.smsCode.isNullOrEmpty()){
comingCode = credential.smsCode!!
progressBarOnayKod.visibility = View.GONE
Log.e("Success","on verificationcompleted sms: " + comingCode)}
else{
Log.e("Error","onverification has not completed")
}
}
override fun onVerificationFailed(e: FirebaseException) {
Log.e("Error: ",e.localizedMessage)
progressBarOnayKod.visibility = View.GONE
}
override fun onCodeSent(verificationId: String,token: PhoneAuthProvider.ForceResendingToken) {
verificationID = verificationId
progressBarOnayKod.visibility = View.VISIBLE
Log.e("Codesent","oncodesent worked")
}
}
}
#Subscribe (sticky = true)
internal fun onTelefonEvent(kayitBilgileri: EventBusDataEvents.KayitBilgileriniGonder){
comingNumber = kayitBilgileri.telNo.toString()
Log.e("test",comingNumber)
}
override fun onAttach(context: Context) {
super.onAttach(context)
EventBus.getDefault().register(this)
}
override fun onDetach() {
super.onDetach()
EventBus.getDefault().unregister(this)
}
}
first set the sha1 to firebase setting and generate google config.json then add to poject's root directory and add to build.gradle dependency.
it'll work properly
resources: https://github.com/firebase/quickstart-android/issues/283

LazyColumn not update when data in LiveData update

I want to create chat app UI using ViewModel but when I send button the List in ViewModel update but LayzColumn not update it data. I don't know why it's not working.
My ChatViewModel
class ChatViewModel: ViewModel() {
private val _messages: MutableLiveData<MutableList<String>> = MutableLiveData(mutableListOf(""))
val messages: LiveData<MutableList<String>> get() = _messages
fun add(message: String) {
_messages.value?.add(message)
Log.d("Haha", "${_messages.value?.size}")
_messages.notifyObserver()
}
}
fun <T> MutableLiveData<T>.notifyObserver() {
this.value = value
}
My LazyColumn
val messages by chatViewModel.messages.observeAsState(mutableListOf(""))
LazyColumn(modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
items(messages.toList()) { message ->
SenderChat(message = message)
}
}

Codename One: Connecting and populating a drop-down menu with an SQLite database

I am trying to connect an SQLite database file to a picker component (accepting strings). This should act similar to a drop-down menu. I have tried to follow previous advice and examples, but without success.
As indicated in a previous post, I have saved the database file in the source folder of the application. View of the source folder where I have saved the database file (highlighted).
The code I have used to implement my app is as follows with the below layout.
//-----------------------
database code
//-----------------------
public class MyApplication {
private Form current;
private Resources theme;
public void init(Object context) {
theme = UIManager.initFirstTheme("/theme");
// Pro only feature, uncomment if you have a pro subscription
// Log.bindCrashProtection(true);
}
private Container Home() {
Container home = new Container(new BoxLayout(BoxLayout.Y_AXIS));
return home;
}
private Container AddItem() {
Container addItem = new Container(new BoxLayout(BoxLayout.Y_AXIS));
TextArea item = new TextArea("Add Item");
addItem.addComponent(item);
Picker selectItem = new Picker();
selectItem.setType(Display.PICKER_TYPE_STRINGS);
//----------------------------------------------------------------------------------
Database db = null;
Cursor cur = null;
try {
db = Display.getInstance().openOrCreate("FoodAndBeverage.db");
if(selectItem.getText().startsWith("Still Water")) {
cur = db.executeQuery(selectItem.getText());
int columns = cur.getColumnCount();
addItem.removeAll();
if(columns > 0) {
boolean next = cur.next();
if(next) {
ArrayList<String[]> data = new ArrayList<>();
String[] columnNames = new String[columns];
for(int iter = 0 ; iter < columns ; iter++) {
columnNames[iter] = cur.getColumnName(iter);
}
while(next) {
Row currentRow = cur.getRow();
String[] currentRowArray = new String[columns];
for(int iter = 0 ; iter < columns ; iter++) {
currentRowArray[iter] = currentRow.getString(iter);
}
data.add(currentRowArray);
next = cur.next();
}
Object[][] arr = new Object[data.size()][];
data.toArray(arr);
addItem.add(BorderLayout.CENTER, new Table(new DefaultTableModel(columnNames, arr)));
} else {
addItem.add(BorderLayout.CENTER, "Query returned no results");
}
} else {
addItem.add(BorderLayout.CENTER, "Query returned no results");
}
} else {
db.execute(selectItem.getText());
addItem.add(BorderLayout.CENTER, "Query completed successfully");
}
addItem.revalidate();
} catch(IOException err) {
Log.e(err);
addItem.removeAll();
addItem.add(BorderLayout.CENTER, "Error: " + err);
addItem.revalidate();
} finally {
Util.cleanup(db);
Util.cleanup(cur);
}
//---------------------------------------------------------------------------------------------
addItem.addComponent(selectItem);
TextField quantity = new TextField("", "Quantity (ml or g)", 4, TextArea.NUMERIC);
addItem.addComponent(quantity);
Button add = new Button("Add");
addItem.addComponent(add);
TextArea results = new TextArea("Results");
addItem.addComponent(results);
return addItem;
}
private Container Settings() {
Container settings = new Container(new BoxLayout(BoxLayout.Y_AXIS));
TextArea nutrients = new TextArea("Target");
settings.addComponent(nutrients);
TextField volume = new TextField("", "Volume (ml)", 4, TextArea.NUMERIC);
settings.addComponent(volume);
TextArea duration = new TextArea("Hydration Duration");
settings.addComponent(duration);
settings.add("Start:");
Picker start = new Picker();
start.setType(Display.PICKER_TYPE_TIME);
settings.addComponent(start);
settings.add("End:");
Picker end = new Picker();
end.setType(Display.PICKER_TYPE_TIME);
settings.addComponent(end);
Button save = new Button("Save");
settings.addComponent(save);
return settings;
}
public void start() {
if(current != null)
{
current.show();
return;
}
Form home = new Form("Hydrate", new BorderLayout());
Tabs t = new Tabs();
t.addTab("Home", Home());
t.addTab("Intake", AddItem());
t.addTab("Settings", Settings());
home.add(BorderLayout.NORTH, t);
home.show();
}
public void stop() {
current = Display.getInstance().getCurrent();
}
public void destroy() {
}
}
I would therefore appreciate any advice and guidance on exactly where I am going wrong and how to implement the suggested changes in my code.
I'm assuming the file under src does indeed end with the extension db as the Windows hidden extensions nonsense is turned on.
This code will NOT open a db placed in src:
db = Display.getInstance().openOrCreate("FoodAndBeverage.db");
You need to do something like this to implicitly initialize the DB the first time the app is installed:
String path = Display.getInstance().getDatabasePath("FoodAndBeverage.db");
FileSystemStorage fs = FileSystemStorage.getInstance();
if(!fs.exists(path)) {
try (InputStream is = Display.getInstance().getResourceAsStream(getClass(), "/FoodAndBeverage.db");
OutputStream os = fs.openOutputStream(path)) {
Util.copy(is, os);
} catch(IOException err) {
Log.e(err);
}
}
db = Display.getInstance().openOrCreate("FoodAndBeverage.db");
Notice that the code above doesn't check for updates of the DB so assuming the DB is read only you might want to update/merge it with app updates.
The above code doesn't work on Android device, this works only on simulator. I have tested multiple times in the android device. In the real android device ,the database is not loaded at all, shows sql exception error
"No such table sql exception".
Looks like preloaded sqlite .db file is never tested on real Android device.

Caliburn.Micro Screen.CanClose() and MessageDialog().ShowAsync()

I'm currently using Caliburn.Micro 2.0 for my Windows Phone 8.1 project (Universal App) and I'm having problem with conditionally cancelling page close after user clicks a MessageDialog button.
It seems that Caliburn closes page after leaving CanClose() method, not waiting for the callback which is called after async MessageDialog.
public class MyViewModel: Screen
{
public override async void CanClose(Action<bool> callback)
{
MessageDialog dlg = new MessageDialog("Close?","Confirmation");
dlg.Commands.Add(new UICommand() { Id = 0, Label = "Yes" });
dlg.Commands.Add(new UICommand() { Id = 1, Label = "No" });
var result = await dlg.ShowAsync();
callback((int)result.Id == 0);
}
}
The only solution I have at the moment is set a field with a flag indicating if the page can be closed. On the user attempt to navigate back I tell Caliburn to abort the close and I display the confirmation dialog. When I get the result I set the flag to true and navigate back manually. This causes another call to CanClose, but this time I set the callback to true and skip the dialog part.
I don't like this solution much, but it is only way I managed to solve this problem.
private bool canClose = false;
public override async void CanClose(Action<bool> callback)
{
callback(canClose);
if (!canClose)
{
MessageDialog dlg = new MessageDialog("Close?","Confirmation");
dlg.Commands.Add(new UICommand() { Id = 0, Label = "Yes" });
dlg.Commands.Add(new UICommand() { Id = 1, Label = "No" });
var result = await dlg.ShowAsync();
if ((int)result.Id == 0)
{
canClose = true;
navigationService.GoBack();
}
}
}
PS: I don't use MessageDialog directly in my ViewModel, I'm using a dialog service interface for popups. I just used it here to demonstrate the issue.
While the enhancement CanClose isn't set, this is my approach utilize Navigating event to solve this 'problem'
If user could just GoBack() would be easy to handle, but in my case, there's many options to navigate. So, the only way that I found to solve it is described below:
public MyViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
_navigationService.Navigating += OnGoBack;
}
private async void OnGoBack(object sender, NavigatingCancelEventArgs e)
{
e.Cancel = true;
var dlg = new MessageDialog("Close?", "Confirmation"); //Dialog for demo purpose only!
dlg.Commands.Add(new UICommand() { Id = 0, Label = "Yes" });
dlg.Commands.Add(new UICommand() { Id = 1, Label = "No" });
var result = await dlg.ShowAsync();
if ((int) result.Id != 0) return;
_navigationService.Navigating -= OnGoBack;
if (e.NavigationMode == NavigationMode.Back)
_navigationService.GoBack();
else
{
var myViewModel = Type.GetType($"YourNameSpaceViewModels.{e.SourcePageType.Name}Model");
_navigationService.NavigateToViewModel(myViewModel);
}
}
Explaination:
$"YourNameSpaceViewModels.{e.SourcePageType.Name}Model"
Here I get the full path to my class where user want to goto
And so, I navigate to it _navigationService.NavigateToViewModel(myViewModel);

Resources