Android Room.databaseBuilder().createFromFile() silently failing - android

I currently have the following working code to import an existing user-chosen SQLite db file from the device into my app, which already had a db:
// here goes code to .close() the app's db if isOpen()
// val userChosenUri: Uri? = data?.data
val outStream = getDatabasePath(DATABASE_NAME).outputStream()
val inStream = userChosenUri.toString().let {
userChosenUri?.let { it1 ->
contentResolver.openInputStream(
it1
)
}
}
inStream.use { input ->
outStream.use { output ->
if (output != null) {
input?.copyTo(output)
}
}
}
I'm trying to replace it with code I found on the Android Room guide:
Room.databaseBuilder(this, AppDatabase::class.java, DATABASE_NAME)
.createFromFile(File(userChosenUri!!.path))
.build()
But this last snippet doesn't seem to do anything. It doesn't crash, but it also doesn't replace the db.
Does anyone know what I'm missing?

AFAIK, createFromFile() only creates the database from the file if the database does not already exist. If you want to use this for an import or restore-from-backup scenario, I think that you would need to close and delete the existing database before trying to use your latter code snippet.

Related

Cannot delete multiple documents in Firebase Cloud Firestore

I'm developing an Android Q&A application. I'd like users to delete all of their posts when they delete their accounts. I found this article so I tried the same code, but it didn't work.
This is my code.
#Composable
fun DeleteAccounts(navController: NavController, uid: String) {
val db = Firebase.firestore
Button(
onClick = {
val batch = db.batch()
db.collection("posts")
.whereEqualTo("uid", "$uid")
.get().result.forEach {
batch.delete(it.reference)
}
batch.commit()
.addOnSuccessListener {
navController.navigate("Login")
}
}
And this is the error message.
java.lang.IllegalStateException: Task is not yet complete
at com.google.android.gms.common.internal.Preconditions.checkState(com.google.android.gms:play-services-basement##18.1.0:2)
at com.google.android.gms.tasks.zzw.zzf(com.google.android.gms:play-services-tasks##18.0.1:1)
at com.google.android.gms.tasks.zzw.getResult(com.google.android.gms:play-services-tasks##18.0.1:1)
at com.example.**app.DeleteAccountsViewKt$DeleteAccounts$1$5$1$1.invoke(DeleteAccountsView.kt:124)
at com.example.**app.DeleteAccountsViewKt$DeleteAccounts$1$5$1$1.invoke(DeleteAccountsView.kt:117)
What am I doing wrong? Thank you.
I tried another code and this works. But I don't know if this is correct.
val batch = db.batch()
db.collection("posts")
.whereEqualTo("uid", uid)
.get()
.addOnSuccessListener { result ->
for (document in result) {
batch.delete(document.reference)
}
batch.commit()
It's possible there are too many documents to delete in your Android client. If you read the documentation here: https://firebase.google.com/docs/firestore/manage-data/delete-data#collections It says that batch deleting documents in a client is not recommended.
You could try writing a Cloud Function to batch delete a lot of documents instead.

Problem with reading a .Json File in Kotlin/Android

I am writing something to read a Json in Android with Kotlin.
The Goal is to either start the ChooseLoginActicity if there is no User.Json available or to otherwise read the Json and start the MainActivity.
The problem is that the line to open the File into a Input Stream always gets an FileNotFoundException even if the User.Json exists with the Permission -rw-rw---- and got created with Context.MODE_PRIVATE, by the same Application.
This Function got called in the onCreate Function of the Activity.
fun readJsonFile() {
try {
val inputStream = openFileInput("User.json")
val json = inputStream.bufferedReader().use { it.readText() }
val gson = Gson()
val user = gson.fromJson(json, User::class.java)
CurrentUser.initUser(user.id,user.name)
startActivity(Intent(this, MainActivity::class.java))
} catch (e: FileNotFoundException) {
startActivity(Intent(this, ChooseLoginActivity::class.java))
}
}
Make sure you wrote the correct file name. Pay attention about case sensitivity.
The code itself should work fine.

W/DocumentsContract: Failed to create document | Error when creating a saving a file on Android/Kotlin

I am trying to save/export a file on the user Documents shared folder so it can be persistent if the application is deleted (it's an export of the user work on the application). Following the official documentation to create a save a file on the shared folder, I have this basic implementation:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { writeInFile(it, "this is a test") }
}
}
}
private fun createFile() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply{
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, "filename.txt")
putExtra(DocumentsContract.EXTRA_INITIAL_URI, Environment.DIRECTORY_DOCUMENTS)
}
startActivityForResult(intent, WRITE_REQUEST_CODE)
//this.registerActivity.launch(intent)
}
private fun writeInFile(uri: Uri, text: String) {
val outputStream: OutputStream
try {
Log.i("export", uri.toString())
outputStream = contentResolver.openOutputStream(uri)!!
val bw = BufferedWriter(OutputStreamWriter(outputStream))
bw.write(text)
bw.flush()
bw.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
The createFile function is called from a click event to export the data.
The Activity for choosing the folder to save is launching, but saving is failing. I am getting the following error:
2021-06-23 18:24:43.268 2853-2871/? E/DatabaseUtils: Writing exception to parcel
java.lang.IllegalArgumentException: Parent document isn't a directory
at com.android.internal.content.FileSystemProvider.createDocument(FileSystemProvider.java:244)
at com.android.providers.downloads.DownloadStorageProvider.createDocument(DownloadStorageProvider.java:207)
at android.provider.DocumentsProvider.callUnchecked(DocumentsProvider.java:1124)
at android.provider.DocumentsProvider.call(DocumentsProvider.java:1067)
at android.content.ContentProvider.call(ContentProvider.java:2448)
at android.content.ContentProvider$Transport.call(ContentProvider.java:517)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:295)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
2021-06-23 18:24:43.269 1559-1680/? W/DocumentsContract: Failed to create document
java.lang.IllegalArgumentException: Parent document isn't a directory
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
at android.content.ContentProviderProxy.call(ContentProviderNative.java:732)
at android.content.ContentProviderClient.call(ContentProviderClient.java:603)
at android.content.ContentResolver.call(ContentResolver.java:2395)
at android.provider.DocumentsContract.createDocument(DocumentsContract.java:1371)
at com.android.documentsui.DocumentsAccess$RuntimeDocumentAccess.createDocument(DocumentsAccess.java:157)
at com.android.documentsui.picker.CreatePickedDocumentTask.run(CreatePickedDocumentTask.java:79)
at com.android.documentsui.picker.CreatePickedDocumentTask.run(CreatePickedDocumentTask.java:42)
at com.android.documentsui.base.CheckedTask.doInBackground(CheckedTask.java:65)
at android.os.AsyncTask$3.call(AsyncTask.java:394)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at com.android.documentsui.ProviderExecutor.run(ProviderExecutor.java:104)
I just found that the issue wasn't in my implementation, but my Android emulator. I tried to create a folder from the file manager (outside my application) and I was getting the same error. So, I tried testing my application on a physical device, it worked perfectly!
The issue with my emulator was that the SD card was corrupted (either it wasn't set up or something went wrong at some point without me realizing it). Resetting that fixed the issue

Is it possible to compress a prepackaged database for room persistence library?

I have a huge database of about 150mb. Can I put the compressed version of the database e.g. zip in the asset folder for room to use or is that not possible?
PS: android studio apk compression is not sufficient enough
First you need a function which can unzip archive to a some directory:
// unzip(new File("/sdcard/whatToUnzip.zip"), new File("/toThisFolder"));
fun unzip(zipFile: File, targetDirectory: File) {
unzip(BufferedInputStream(FileInputStream(zipFile)), targetDirectory)
}
fun unzip(zipInputStream: InputStream, targetDirectory: File) {
try {//BufferedInputStream(zipFileStream)
ZipInputStream(zipInputStream).use { zipInput ->
var zipEntry: ZipEntry
var count: Int
val buffer = ByteArray(65536)
while (zipInput.nextEntry.also { zipEntry = it } != null) {
val file = File(targetDirectory, zipEntry.name)
val dir: File? = if (zipEntry.isDirectory) file else file.parentFile
if (dir != null && !dir.isDirectory && !dir.mkdirs()) throw FileNotFoundException(
"Failed to ensure directory: " + dir.absolutePath
)
if (zipEntry.isDirectory) continue
FileOutputStream(file).use { fileOutput ->
while (zipInput.read(buffer).also { count = it } != -1) fileOutput.write(
buffer,
0,
count
)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
I got it out of that stackoverflow's thread. Please read a thread to get more details. Then I added two method to work with a file from app's asset folder:
fun unzipAsset(assetsFilePath: String, context: Context, targetDirectory: File) {
unzip(context.assets.open(assetsFilePath), targetDirectory)
}
fun Context.unzipAsset(assetsFilePath: String, targetDirectory: File) = unzipAsset(
assetsFilePath,
this,
targetDirectory
)
Now we can unzip file to folder. To avoid copying an unzipped db file by room when I use createFromAsset or createFromFile methods of Room.databaseBuilder I want to unzip file to apps databases folder which used by room to store db file. That why I need additional methods to get db folder path and to check when db file already exist:
fun Context.databaseFolderPath(): File? = this.getDatabasePath("any.db").parentFile
// name – The name of the database file.
fun Context.isRoomDbFileExist(name: String): Boolean {
return this.getDatabasePath(name)?.exists() ?: false
}
And now, how to use all thinks together:
abstract class AppDatabase : RoomDatabase() {
companion object {
private const val DB_NAME = "sunflower-db"
// Create and pre-populate the database. See this article for more details:
// https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785
private fun buildDatabase(context: Context): AppDatabase {
if(!context.isRoomDbFileExist(DB_NAME)) {
// unzip db file to app's databases directory to avoid copy of unzipped file by room
context.unzipAsset("sunflower-db.zip", context.databaseFolderPath()!!)
// or unzip(File("your file"), context.databaseFolderPath()!!)
}
return Room.databaseBuilder(context, AppDatabase::class.java, DB_NAME)
//.createFromAsset(DB_NAME) // not zipped db file
.build()
}
}
}
I test this code on nice open source project - sunflower. Next I want to show screen with project structure , where sunflower-db.zip located:
The approach above works but You shouldn't take this sample as right or best solution. You should to think about avoid unzipping process from main thread. May be will be better if you implement your own SupportSQLiteOpenHelper.Factory(look like complicated).

FirebaseFirestore multiple processes

I have an App with Firestore. I have a lot of Repositories. They work from Firestore. When I call 2 method in same time then I got an error.
class CommentRepository : CommentRepositoryInterface {
val firebaseFirestore = FirebaseFirestore.getInstance()
companion object {
const val COLLECTION_NAME = "post_comments"
const val COMMENT_POST_ID_KEY = "postid"
}
override fun getPostCommentsById(postId: String): Observable<CommentModel> {
return Observable.create { subscriber ->
firebaseFirestore.collection(COLLECTION_NAME)
.whereEqualTo(COMMENT_POST_ID_KEY, postId)
.get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
for (document in task.result) {
if (document.exists()) {
val documentModel = document.toObject(CommentModel::class.java)
subscriber.onNext(documentModel)
}
}
subscriber.onComplete()
} else {
subscriber.onError(task.exception!!) // TODO
}
}
}
}
}
The another one is almost same like that, but that one is using another collection.
So when I called these functions, then I got the next error:
Internal error in Firestore (0.6.6-dev).
Caused by: java.lang.RuntimeException: Failed to gain exclusive lock to the Firestore client's offline persistence. This generally means you are using Firestore from multiple processes in your app. Keep in mind that multi-process Android apps execute the code in your Application class in all processes, so you may need to avoid initializing Firestore in your Application class. If you are intentionally using Firestore from multiple processes, you can only enable offline persistence (i.e. call setPersistenceEnabled(true)) in one of them.
In the MyApplication class I tried to set the Singleton's of firestore settings.
val settings = FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(true)
.build()
FirebaseFirestore.getInstance().firestoreSettings = settings
I found it in Firestore's Doc:
For Android and iOS, offline persistence is enabled by default.
Anyone have idea to solve this problem?
I've cleared the App's Caching and the problem solved.
Do it or just remove from the phone! :)

Categories

Resources