How combine Dagger2 and coroutines? - android

I'am using dagger2 + retrofit + coroutines + firebaseRemoteConfig
I can't get totoName updated every time I change the remote configs in the Firebase console. The problem is my use of coroutines... can you help me? Thanks
#Provides
#Singleton
#Named("toto")
suspend fun provideToto(remoteConfig: FirebaseRemoteConfig): String {
var totoName = fetchToto(remoteConfig)
return totoName
}
suspend fun fetchToto(remoteConfig: FirebaseRemoteConfig): String {
var totoName = remoteConfig.getString("toto_name")
withContext(Dispatchers.IO) {
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
totoName = remoteConfig.getString("toto_name")
}
}
return totoName
}

I'm not sure that Dagger itself is configured to work with suspensions. Dagger works in pass. First, it asks for the object, if the object isn't ready it ask for object creation. There isn't any inbuilt functionality for awaiting for the suspension to complete. Moreover, dagger objects are lazy initialized Singletons hence you only get what you create. So you already saving memory. Plus firebase is initialized at the application level (preloaded). So you always get ready to use firebase instances. So no need to suspend for creation.

Of course that doesn't work. First of all if you use #Singleton there fill be only one fetch() for the entire app.
Next, I'm not really sure you may do that with dagger at all. Your methods should be located in the respective classes and not on some dependency graph.

Related

Android Hilt - update references to a given dependency

I am working on refactoring an android application (I'm not the original author) which uses a pre-created sqlite database file received from the backend. It is done like this because my client's use case needs a local database in which one of the tables can have 1 million rows in some cases. It is a stock-taking app for a rugged device which needs to work offline which means that the device needs to store the entire database of all the various products that can be found for the given warehouse so that the workers can see a product's information after scanning it's barcode. Every day at the start of the work on a new project, the pre-created database gets acquired from the backend and is used for the remainder of the project for the rest of the day.
I use Room for the database and also use Hilt. Normally everything works fine. The problem arises when/if the client uses a functionality in which the app can re-download the pre-created database from the backend which means that the entire database file Room uses gets rewritten. To avoid having references to a database that no longer exists, I close the database by calling my closeDatabase() method which then later gets recreated. The database class is the following (I shortened it and changed names due to NDA reasons):
#Database(
entities = [
ItemTable::class
],
exportSchema = false,
version = 1
)
abstract class ProjectDatabase : RoomDatabase() {
abstract fun roomItemDao(): ItemDao
companion object {
#Volatile
private var INSTANCE: ProjectDatabase? = null
fun getDatabase(): ProjectDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
MyApplication.appContext,
ProjectDatabase::class.java,
getDbNameWithPath()
).createFromAsset(getDbNameWithPath())
.build()
INSTANCE = instance
instance
}
}
fun closeDatabase() {
INSTANCE?.close()
INSTANCE = null
}
private fun getDbNameWithPath(): String {
return MyApplication.appContext.filesDir.toString() +
File.separator + Constants.PROJECT_DATABASE_NAME
}
}
}
I also use a Hilt module for the database like this:
#Module
#InstallIn(SingletonComponent::class)
class ProjectDatabaseModule {
#Provides
fun provideProjectDatabase(): ProjectDatabase {
return ProjectDatabase.getDatabase()
}
#Provides
fun provideItemDao(
projectDatabase: ProjectDatabase
): ItemDao {
return projectDatabase.roomItemDao()
}
}
My problem is that when I set the INSTANCE to null, then the next call to getDatabase() creates a new instance, but all the references previously created by Hilt for the various classes still reference the old instance.
If I don't call INSTANCE = null then the database doesn't get reopened. If I don't close the database, then Room goes insane due to having its entire underlying database completely changed. Previously I always called getDatabase().xyz which worked but was kinda ugly, thus I started to use Hilt for it.
Is there a solution for this via Hilt? I'm afraid I'll have to go back to my old solution. Even if I change my scope to something else, the already existing classes will use the old reference.
Basically what I wish for is a call to ProjectDatabase.getDatabase.roomItemDao() every time I call a method of ItemDao.
I decided that the best solution in this case is to either ask the user to restart your app or do it programmatically according to your given use case.
Thanks to #MikeT for his reassuring comment.

'Cannot access database on the main thread since it may potentially lock the UI for a long period of time' Also, I Accessed room using coroutine

Initially I directly accessed a viewModel funtion which launched a viewModel scope coroutine for the query I was running. That had the same error.
Then, I changed the viewModel function to a suspend function and called it from a coroutine in the fragment. that didn't work either.
So, I made it such that the function was called inside a coroutine which then ran another coroutine in the viewModel scope. That gave the same outcome.
I thought it might be too much load to call it during fragment creation. so I tried calling the viewModel function using a button onclick listener. again crashed.
I ran the same query in the database inspector where it worked fine. so, the query isn't the issue either.
In the below screenshot I have included every necessary detail regarding the issue. Just focus on the highlighted content. start from pass List Fragment(top-left tab). From there, a call to the viewModel function in the top right tab. from there the DAO right below it. then the data class below it.
Android studio screenshot -
the viewModel function -
fun resetAllAccess(){
viewModelScope.launch {
passwordDao.resetAccessForAll()
}
}
The DAO funtion -
#Query("UPDATE password_data SET access = 0 WHERE access = 1")
fun resetAccessForAll()
Data class for the database -
#Entity(tableName = "password_data")
data class PasswordData(
#PrimaryKey(autoGenerate = true) val id: Int = 0,
#ColumnInfo(name = "service_name") var serviceName: String,
#ColumnInfo(name = "service_password") var servicePassword: String,
#ColumnInfo(name = "is_an_application") var isAnApplication: Boolean = false,
#ColumnInfo(name = "use_finger_print") var useFingerPrint: Boolean = true,
#ColumnInfo(name = "access") var access: Boolean = false
)
the call being made from the fragment -
CoroutineScope(Dispatchers.IO).launch { viewModel.resetAllAccess() }
I replaced this funtion -
fun resetAllAccess(){
viewModelScope.launch {
passwordDao.resetAccessForAll()
}
}
-with
fun resetAllAccess(){
CoroutineScope(Dispatchers.IO).launch {
passwordDao.resetAccessForAll()
}
}
so now, it is no longer running on the main thread.
It's because you are using the viewModelScope. Lots of people don't know this, but viewModelScope is actually hardcoded to use the main thread instead of another one.
You can find this info in Google official documentation:
Note: The viewModelScope property of ViewModel classes is hardcoded to Dispatchers.Main. Replace it in tests by using Dispatchers.setMain with a TestCoroutineDispatcher as explained in the Easy coroutines in Android: viewModelScope blog post.
So you probably want to either pass the the coroutine scope into the view model class constructor (preferred way) or create one directly in the view model. Then you should just use this to launch the coroutine.
Another practice that is commonly used is to create a scope with both view model and a custom scope (passed through the view model constructor).
For example:
class ViewModelClass(customCoroutineScope: CoroutineScope): ViewModel {
private val backgroundScope: CoroutineContext = customCoroutineScope.coroutineContext + viewModelScope.coroutineContext
fun resetAllAccess(){
backgroundScope.launch {
passwordDao.resetAccessForAll()
}
}
}
You can also find more info here about how viewModelScope works under the hood.
There's also another option, but I wouldn't recommend this at all, for obvious reasons, that is to allow Room to run queries in the main thread by using the allowMainThreadQueries in the Room builder.
If it is Dispatcher.Main, you should change it to IO because the UI is blocked.
fun upsert(item: ShoppingItem)=GlobalScope.launch(Dispatchers.IO) { repository.upsert(item) }

Coroutine keeps crashing without showing error

I'm developing in MVP. In my Presenter, I call my Repository thanks to the suspend function. In this suspend function, I launch a Coroutine - not on the main thread.
When this coroutine is finished, I want to execute some code: I am using withContext() in order to do so.
In my Repository, I am launching a Coroutine (and maybe I am wrong) to insert my data, using DAO, in my Room database.
When I debug my application, it goes into my Presenter but crashes before going into my Repository.
Presenter
override suspend fun insertUserResponse(activity: Activity, data: UserResponse) {
scope.launch(Dispatchers.IO) {
try {
userResponseRepository.insertUserResponse(data)
withContext(Dispatchers.Main) {
redirectToClientMainPage(activity)
}
} catch (error: Exception) {
parentJob.cancel()
Log.e("ERROR PRESENTER", "${error.message}")
}
}
}
Repository
override suspend fun insertUserResponse(userResponse: UserResponse) {
GlobalScope.launch(Dispatchers.IO) {
try {
val existingUser: UserResponse? =
userResponseDAO.searchUserByID(userResponse.profilePOJO.uniqueID)
existingUser?.let {
userResponseDAO.updateUser(userResponse)
} ?: userResponseDAO.insertUser(userResponse)
} catch (error: Exception) {
Log.e("ERROR REPOSITORY", "${error.message}")
}
}
}
I have no error shown in my logcat.
EDIT:
Scope initialization
private var parentJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + parentJob
private val scope = CoroutineScope(coroutineContext)
val uiContext: CoroutineContext = Dispatchers.Main (initialized in my class constructor)
Stack trace
I finally found the answer!
Thanks to #sergiy I read part II of this article https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd and it mentions that you can't catch error except Throwable and CancellationException.
So instead of catching Exception, I traded it for Throwable. And finally I had an error shown in my logcat.
I am using Koin to inject my repository and all. I was missing my androidContext() in my Koin application.
That's it.
Without stacktrace it's hard to help you.
Here is an article, that might be helpful. Replacing ViewModel mentioned in the article with Presenter in your case you can get several general recommendations in using coroutines:
As a general pattern, start coroutines in the ViewModel (Read: Presenter)
I don't see the need to launch one more coroutine in your case in Repository.
As for switching coroutine's context for Room:
Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower.
I couldn't find the same recommendation in official docs, but it's mentioned in one of the Google code labs.
Both Room and Retrofit make suspending functions main-safe.
It's safe to call these suspend funs from Dispatchers.Main, even though they fetch from the network and write to the database.
So you can omit launch (as well as withContext) in Repository, since Room guarantees that all methods with suspend are main-safe. That means that they can be called even from the main thread. Also you can not to define Dispatchers.IO explicitly in Presenter.
One more thing. If Presenter's method insertUserResponse is suspend, then you call it from another launched coroutine, don't you? In that case, why you launch one more coroutine inside this method? Or maybe this method shouldn't be suspend?

How to pass in parameters to a dagger module from a activity or fragment at runtime

My software specifications are as follows:
Android Studio 3.4
dagger-android 2.16
I have the following class that passes a MapboxGeocoder that will execute and return a response.
class GeocodingImp(private val mapboxGeocoder: MapboxGeocoder) : Geocoding {
override fun getCoordinates(address: String, criteria: String): AddressCoordinate {
val response = mapboxGeocoder.execute()
return if(response.isSuccess && !response.body().features.isEmpty()) {
AddressCoordinate(
response.body().features[0].latitude,
response.body().features[0].longitude)
}
else {
AddressCoordinate(0.0, 0.0)
}
}
}
However, the MapboxGeocoder is generated in a dagger module at compile time. So I have to specify the string for the address and TYPE_ADDRESS.
#Reusable
#Named("address")
#Provides
fun provideAddress(): String = "the address to get coordinates from"
#Reusable
#Provides
#Named("geocoder_criteria")
fun provideGeocoderCriteria(): String = GeocoderCriteria.TYPE_ADDRESS
#Reusable
#Provides
fun provideMapboxGeocoder(#Named("address") address: String, #Named("geocoder_criteria") geocoderCriteria: String): MapboxGeocoder =
MapboxGeocoder.Builder()
.setAccessToken("api token")
.setLocation(address)
.setType(geocoderCriteria)
.build()
#Reusable
#Provides
fun provideGeocoding(mapboxGeocoder: MapboxGeocoder): Geocoding =
GeocodingImp(mapboxGeocoder)
my component class:
interface TMDispatchMobileUIComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: TMDispatchMobileUIApplication): Builder
fun build(): TMDispatchMobileUIComponent
}
fun inject(application: TMDispatchMobileUIApplication)
}
In the main activity I would use this like this as the user can enter in a different address or change the criteria to something else. But as the module are compiled I cannot pass any parameters to them at runtime:
presenter.getAddressCoordinates("this should be the actual address", GeocoderCriteria.TYPE_ADDRESS)
For my injection into the Activity I use the following:
AndroidInjection.inject(this)
Is there any solution to this problem?
The problem you have can be solved using "Assisted injection" approach.
It means that you need a class to be built both using dependencies provided from the existing scopes and some dependencies from the instance's creator, in this case, your main activity. Guice from Google has a nice description of what it is and why it is needed
Unfortunately, Dagger 2 does not have this feature out from the box. However, Jake Wharton is working on a separate library that can be attached to Dagger. Moreover, you can find more details in his talk on Droidcon London 2018, where he dedicated a whole talk section for this question:
https://jakewharton.com/helping-dagger-help-you/
You can recreate your whole component at runtime if you wish, where you'd then pass in the parameters to your module as a constructor parameter. Something like:
fun changeAddress(address: String) {
val component = DaggerAppComponent.builder() //Assign this to wherever we want to keep a handle on the component
.geoModule(GeoModule(address))
.build()
component.inject(this) //To reinject dependencies
}
And your module would look like:
#Module
class AppModule(private val address: String) {...}
This method may be wasteful though, if you're creating many different objects in your component.
A different approach compared to the already given answers would be to get a "Factory" via dagger dependency injection called GeoModelFactory which can create new instances of GeoModel for you.
You can pass the address and type to the factory which creates your instance. For optimization you can either store references for all different address/types that have already been requested (can result in a memory leak if there are a lot of different ones if old ones are not removed) or it could also be enough if you store only the latest instance and in other parts of the code to simply ask the factory to provide you with the GeoModel that has been created last.
The MapboxGeocoder are dynamically constructed at runtime, in this case, dagger doesn't help much as its objective is to help you construct the object graph at compile time like you hand write the code.
So in my opinion, you should create a MapboxGeocoder inside getCoordinates().

Android repository-pattern & RxJava: Use Flowable or Single?

Recently, I´ve read about how important it is to have a Single-Source-Of-Truth (SSOT) when designing an app´s backend (repository, not server-side-backend). https://developer.android.com/topic/libraries/architecture/guide.html
By developing a news-feed app (using the awesome https://newsapi.org/) I am trying to learn more about app architecture.
However, I am unsure of how to design the repository interface for my app.
Btw.: I am using MVVM for my presentation layer. The View subscribes to the ViewModel´s LiveData. The ViewModel subscribes to RxJava streams.
So I came up with 2 approaches:
Approach 1:
interface NewsFeedRepository {
fun loadFeed(): Flowable<List<Article>>
fun refreshFeed(): Completable
fun loadMore(): Completable
}
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Flowable<List<Article>>
fun moreArticles(): Completable
}
interface BookmarkRepository {
fun getBookmarkedArticles(): Flowable<List<Article>>
fun bookmarkArticle(id: String): Completable
}
This approach is primarily using Flowables which emit data if the corresponding data in the underlying SSOT (database) changes (e.g old data gets replaced with fresh data from API, more data was loaded from API, ...). However, I am unsure if using a Flowable for SearchArticleRepository#searchArticles(...) makes sense. As it is like some request/response thing, where maybe a Single might me be more intuitive.
Approach 2:
interface NewsFeedRepository {
fun loadFeed(): Single<List<Article>>
fun refreshFeed(): Single<List<Article>>
fun loadMore(): Single<List<Article>>
}
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Single<List<Article>>
fun moreArticles(): Single<List<Article>>​
}
interface BookmarkRepository {
fun getBookmarkedArticles(): Single<List<Article>>
fun bookmarkArticle(id: String): Single<Article> // Returns the article that was modified. Articles are immutable. ​
}
This approach is using Singles instead of Flowables. This seems very intuitive but if the data in the SSOT changes, no changes will be emitted. Instead, a call to the repository has to be made again. Another aspect to take into account is that the ViewModel may have to manage its own state.
Let´s take the FeedViewModel for example (pseudo-code).
class FeedViewModel : ViewModel() {
// Variables, Boilerplate, ...
val newsFeed: LiveData<List<Article>>
private val articles = mutableListOf<Article>()
fun loadNewsFeed() {
// ...
repository.loadFeed()
//...
// On success, clear the feed and append the loaded articles.
.subscribe({articles.clear(); articles.addAll(it)})
// ...
}
fun loadMore() {
// ...
repository.loadMore()
//...
// On success, append the newly loaded articles to the feed.
.subscribe({articles.addAll(it)})
// ...
}
}
So this might not be crucial for a smaller app like mine, but it definitely can get a problem for a larger app (see state management: http://hannesdorfmann.com/android/arch-components-purist).
Finally, I wanted to know which approach to take and why. Are there any best-practices? I know many of you have already done some larger software-projects/apps and it would be really awesome if some of you could share some knowledge with me and others.
Thanks a lot!
I'd rather go for the first approach using Observables instead of Flowables in your case:
interface NewsFeedRepository {
fun loadFeed(): Observable<List<Article>>
fun refreshFeed(): Completable
fun loadMore(): Completable
}
interface SearchArticleRepository {
fun searchArticles(sources: List<NewsSource>? = null, query: String? = null): Observable<List<Article>>
fun moreArticles(): Completable
}
interface BookmarkRepository {
fun getBookmarkedArticles(): Observable<List<Article>>
fun bookmarkArticle(id: String): Completable
}
I don't see any reason you should necessarily use Flowable for this purpose since you'll never have any OOME related issues checking your repository changes. In other words, for your use case IMHO backpressure is not necessary at all.
Check this official guide which gives us an advice of when to a Flowable over an Observable.
On the other hand, and not related to the question itself, I have serious doubts of what's the purpose of loadMore or moreArticles methods since they return a Completable. Without knowing the context, it may seem you could either refactor the method name by a better name or change the return type if they do what they seem to do by the name.
I believe the first approach is better, Your repo will update the data whenever the data is changed and your view model will be notified automatically and that's cool, while in your second approach you have to call the repo again and that's not really reactive programming.
Also, assume that the data can be changed by something rather than load more event from the view, like when new data added to the server, or some other part of the app changes the data, Now in the first approach again you get the data automatically while for the second your not even know about the changed data and you don't know when to call the method again.

Categories

Resources