Reputation: 31
I'm trying to provide data from SQLite
with Room
library in ContentProvider
and my app uses Hilt
for dependency injection.
@Entity(tableName = "files")
data class FileEntity(
@PrimaryKey(autoGenerate = false)
val url: String,
val fileName: String
)
@Dao
interface FilesDao {
@Query(value = "SELECT * FROM files ORDER BY fileName")
suspend fun getFiles(): List<FileEntity>
}
@Database(entities = [FileEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract val filesDao: FilesDao
}
@Module
@InstallIn(SingletonComponent::class)
object RoomModule {
@Provides
@Singleton
fun provideAppDatabase(
@ApplicationContext context: Context
): AppDatabase = Room.databaseBuilder(
context, AppDatabase::class.java, "app_database"
).build()
@Provides
@Singleton
fun provideFilesDao(
appDatabase: AppDatabase
): FilesDao = appDatabase.filesDao
}
With the code above I can receive an instance of a FilesDao
in a DataSource class
and make the calls normally through suspended functions
. For example:
data class FileModel(
val url: String,
val fileName: String
)
interface FilesDataSource {
suspend fun getFiles(): List<FileModel>
}
class FilesDataSourceImpl @Inject constructor(
private val filesDao: FilesDao
) : FilesDataSource {
override suspend fun getFiles(): List<FileModel>{
val filesEntity = filesDao.getFiles()
return filesEntity.toFilesModel()
}
private fun List<FileEntity>.toFilesModel() = map { fileEntity ->
FileModel(
url = fileEntity.url,
fileName = fileEntity.fileName
)
}
}
To pass the instance via dependency injection using Hilt
to a ContentProvider
I know that it is necessary to create a custom EntryPoint
. For example:
@EntryPoint
@InstallIn(SingletonComponent::class)
interface ContentProviderEntryPoint {
// var appDatabase: AppDatabase
var filesDao: FilesDao
}
In ContentProvider
I have the following code so far:
class MyProvider : ContentProvider() {
private val appContext: Context by lazy {
context?.applicationContext ?: throw IllegalStateException()
}
private val files: List<FileModel> by lazy {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
context = appContext,
entryPoint = ContentProviderEntryPoint::class.java
)
hiltEntryPoint.filesDao.getFiles() // this is a suspend function and I have no idea how to deal...
}
override fun onCreate(): Boolean {
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor {
TODO()
}
override fun getType(uri: Uri): String {
TODO()
}
override fun openAssetFile(
uri: Uri,
mode: String
): AssetFileDescriptor? {
TODO()
}
override fun insert(
uri: Uri,
values: ContentValues?
): Uri = throw UnsupportedOperationException()
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<out String>?
): Int = throw UnsupportedOperationException()
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int = throw UnsupportedOperationException()
}
Now my doubt, how to deal with the suspended function
inside the ContentProvider
?
Additional:
I've seen some examples where a new function (not suspended) was created in the DAO class
to return a Cursor
. For example:
@Dao
interface FilesDao {
@Query(value = "SELECT * FROM files")
fun getFilesCursor(): Cursor
// other suspend functions for datasource layer
}
That way I can call the new function in the ContentProvider
, but Room throws an exception:
class MyProvider : ContentProvider() {
private val appContext: Context by lazy {
context?.applicationContext ?: throw IllegalStateException()
}
private val filesCursor: Cursor by lazy {
val hiltEntryPoint = EntryPointAccessors.fromApplication(
context = appContext,
entryPoint = ContentProviderEntryPoint::class.java
)
hiltEntryPoint.filesDao.getFilesCursor()
}
override fun onCreate(): Boolean {
println(filesCursor) // just to see the output, but this throw an IllegalStateException
return true
}
// ...
}
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
My goal with ContentProvider
is to provide Stickers (files) for WhatsApp. I already have a working version of the app that reads the content directly from a JSON file following the same logic with Hilt
, the issue is that now that I'm migrating to Room
that uses suspended functions
I'm not able to deal with this situation.
Upvotes: 3
Views: 646
Reputation: 1
You can use runBlocking
inside your query method:
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor {
return runBlocking {
// your code here
}
}
But you should call query
from CoroutineScope(Dispatchers.IO)
because runBlocking
blocks the current thread interruptibly until its completion
Upvotes: 0