How to handle Room functions in Android's ContentProvider?

Viewed 39

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.

0 Answers
Related