Dagger 2 component keeping old reference - kotlin

Background
I have multiple dagger scopes in the app
UserScope - scope representing user session
ActivityScope - scope per activity
UserComponent - sub component of CoreComponent
UserManager - singleton that creates UserComponent
HomeComponent - Component that is dependent on UserComponent
Issue
I am trying to update user object under UserScope but somehow the object update doesn't get reflected on it's dependent components. So, once update screen updates the user object and the activity in backstack receives the event for the update & I want to reflect that update in my dagger dependencies.
I tried to null the existing component inside UserManager but it looks like the dependent components still holds it's reference.
Summary
User logs in and opens Home screen. At this moment my user manager has created user component with the user object obtained from API call.
Home screen opens update screen which updates the user by re-creating new userComponent inside UserManager. This also fires an event which tells home screen to fetch the updated user object.
Home screen receives the event. However, HomeViewModel(Screen in backstack) is still referring to old User object injected by dagger.
Questions
Is this because HomeComponent is an independant component? So, it keeps an old reference?
Is there any way to achieve this update?
Preferably without using SubComponent. Because I am using a multi-module(dynamic-feature) setup that helps me if I treat this as a separate component.
CoreComponent
#Singleton
#Component
interface CoreComponent {
val userBuilder: UserComponent.Builder
val userManager: UserManager
fun providerContext(): Context
fun inject(activity: UserActivity)
#Component.Factory
interface Factory {
fun create(
#BindsInstance applicationContext: Context
): CoreComponent
}
}
UserComponent
#UserScope
#Subcomponent
interface UserComponent {
val userManager: UserManager
#UserScope
val user: User
#Subcomponent.Builder
interface Builder {
fun bindUser(#BindsInstance user: User): Builder
fun build(): UserComponent
}
}
UserManager
#Singleton
class UserManager #Inject constructor(private val userBuilder: UserComponent.Builder) {
var userComponent: UserComponent? = null
private set
init {
// creating as a dummy reference
createUser(User("1", "1"))
}
fun createUser(user: User) {
userComponent = null
userComponent = userBuilder.bindUser(user).build()
}
fun logout() {
userComponent = null
}
}
HomeComponent
#Component(
modules = [HomeModule::class],
dependencies = [UserComponent::class]
)
#ActivityScope
interface HomeComponent {
fun inject(activity: HomeActivity)
#Component.Factory
interface Factory {
fun create(
#BindsInstance applicationContext: HomeActivity,
coreComponent: UserComponent,
): HomeComponent
}
}
HomeModule
#Module
class HomeModule {
#ActivityScope
#Provides
fun provideVM(activity: HomeActivity, user: User): HomeViewModel {
val vm by activity.scopedComponent {
HomeViewModel(user)
}
return vm
}
}

If you have components A -> B -> C and decide to swap B for B* then you also need to get rid of any object that was created by/using B or anything that depends on B, so any subcomponents, component dependencies, objects, etc.
In this example you'd recreate C as well (using B* this time) to get a C* (using the new dependencies). Now you have new components A -> B* -> C*, but your code (Activity/Fragment/ViewModel) that was created using B/C needs to be "updated" as well. Since it's not feasible to "update" dependencies later, the easiest way to do this would be to recreate all of them as well, this time using B*/C* for injection.
e.g.
In one app where I allow switching between users, I have my main Activity "Singleton -> Activity" scoped, referencing the user manager and displaying the switch UI. The actual app content (per user) is wrapped in a UserFragment which I'll replace as a whole whenever the user changes since this fragment (and all the UI within it) is Singleton -> Activity -> User -> Fragment scoped.
Since Singleton including the user manager won't change, I don't have to recreate the Activity, so all I need to do is recreate the parts affected (in my case the Fragment).

Related

Hilt Singleton doesn't seems to work in my Service

I'm facing an issue, like if my repository injected was not a Singleton.
I have a repository (in reality many, but let's make it simple) marked as #Singleton
#Module
#InstallIn(SingletonComponent::class)
class AppModule {
#Provides
#Singleton
fun provideSettingsRepository(#ApplicationContext context: Context): SettingsRepository {
return SettingsRepositoryImpl(context)
}
}
Here the implementation of my repository :
class SettingsRepositoryImpl(context: Context) : SettingsRepository {
private var _flow = MutableStateFlow("init value")
override fun getFlow(): StateFlow<String?> = _flow.asStateFlow()
override fun setMyValue(value:String) {
_flow.value = value
}
}
When I use it apart of my service (in some viewModels or others class with DI), it work perfectly. Today I implemented an AIDL service and wanted to do some DI. I had to use field injection because the service constructor has to be empty. It seems like the update made from my application isen't reported on my "TestApplication" who consume the Service (like if I had two instance of my repository).
Here the code of my service :
#AndroidEntryPoint
class AppService : Service() {
#Inject lateinit var settingsRepository: SettingsRepository
fun someActionOnMyRepository() {
settingsRepository.setMyValue("whatever")
}
}
When I set the value from my UI (viewModel or any other class who as the repository injected), it's not updated in my service. The flow doesn't contains the new value (tested by debug or logcat).
I'm expecting my settingsRepository to be a singleton. What am I missing ? Is it because the field injection ?
Best regards
Ok, the problem was not from Hilt but about how i declared my service in my AndroidManisfest.xml
<service android:name=".services.MyAppService"
android:process=":remote" <----- THIS
android:exported="true">
Making it like that make it on another process. That mean it's like another app instance (so no more Singleton / SharedPreferences).

How can I pass data from my android library to my app after network calls?

I have made a library that makes a network call and fills a data class on success
data class someData(
val id: String,
val name: String,
)
I'm using moshi and retrofit for the network calls
Once the network call is successful and the data class is filled, I need to send this to the app module
How is this done when we use internal data class and internal viewmodels?
My library viewmodel should pass the data to the app viewmodel
The app uses a button to launch the library
The library has a UI with jetpack compose, it collects information and makes the necessary network calls, and fills the data class on success
How can the library close the compose UI and return the data?
The new "standard practice" way recommended by Google would be to create an ActivityResultContract in your library.
First, you need to make your data class Parcelable. This is most easily done using Parcelize (instructions here).
#Parcelize
data class SomeData(
val id: String,
val name: String,
)
In your library's activity, when you have created an instance of your SomeData class, you should set it as your activity result before finishing the activity:
val resultIntent = Intent().apply {
putExtra("someData", someDataInstance)
}
setResult(Activity.RESULT_OK, resultIntent)
finish()
Then create a contract for your library that launches your library's Activity, and extracts your data class from your activity's results. In this example, there is no required input for the library, so I'm just using Unit as the input type.
Note the return type should be nullable to handle the case where the user exits the library activity before finishing waiting for the fetched result.
class FetchSomeData: ActivityResultContract<Unit, SomeData?>() {
override fun createIntent(context: Context, unit: Unit) =
Intent(context, MyLibraryActivity::class.java)
override fun parseResult(resultCode: Int, result: Intent?) : SomeData? {
if (resultCode != Activity.RESULT_OK) {
return null
}
return result?.getParcelableExtra("someData")
}
}
Then users of your library will register the contract in a class property and use it in one of their button clicks, as explained in the contracts documentation. For example:
private val fetchSomeData = registerForActivityResult(FetchSomeData()) { someData: SomeData? ->
if (someData == null) {
// action was canceled
} else {
// do something with someData
}
}
//...
someButton.setOnClickListener {
fetchSomeData.launch()
}

Usage of Dagger2 outside Android

I've recently started to learn Dagger. In order to do that, i've decided to write a simple console application to get the feeling of how various dagger features (like modules, component, subcomponents and component dependencies) fit together in an app architecture. As I don't really understeand it and given how hard it is to find an application sample created with dagger2 which is not Android app, i've decided to open a question here.
The first and probably most important question is: is dagger2 even ment to be used outside android?
If yes, then lets consider a simple application architecture: we have the data layer, service layer and ui layer
Data layer might consist of some kind of facade:
(Following code snippets will be written in Kotlin)
class Entity(var id: Int)
interface Repository {
fun findEntityById(id: Int): Entity?
fun deleteEntity(entity: Entity): Boolean
fun saveEntity(entity: Entity): Boolean
fun findAllEntities(): List<Entity>
}
Then i could have a couple of implementations of this facade:
class InMemoryRepository #Inject constructor() : Repository {
private val entities: MutableList<Entity> = LinkedList()
override fun findEntityById(id: Int): Entity? = entities.firstOrNull { it.id == id }
override fun deleteEntity(entity: Entity) = entities.remove(entity)
override fun saveEntity(entity: Entity) = entities.add(entity)
override fun findAllEntities(): List<Entity> = LinkedList(entities)
}
For which i would have modules:
#Module
interface InMemoryPersistenceModule {
#Singleton
#Binds
fun bindRepository(rep: InMemoryRepository): Repository
}
Service layer would be simpler:
#Singleton
class Service #Inject constructor(repository: Repository) {
fun doSomeStuffToEntity(entity: Entity) {}
}
#Singleton
class AnotherService #Inject constructor(repository: Repository) {
fun doSomeStuffToEntity(entity: Entity) {}
}
But it gets a little bit unlcear when it comes to the UI layer. Lets say i have some kind of android-like activity:
interface Activity : Runnable
And some kind of class that manages those activities:
class UserInterfaceManager {
val activityStack: Stack<Activity> = Stack()
val eventQueue: Queue<Runnable> = LinkedList()
fun startActivity(activity: Activity) = postRunnable {
activityStack.push(activity)
activity.run()
}
fun postRunnable(callback: () -> Unit) = eventQueue.add(callback)
fun stopActivity() { TODO() }
//other
}
How does dagger fit into this scenario? The articles i have read about the the dagger with android suggest createing the application component to inject my activites:
#Singleton
#Component(modules = [InMemoryPersistenceModule::class])
interface ApplicationComponent {
fun injectSomeActivity(activity: SomeActivity)
// and more
}
But then, where would the injection go to? It does't really make sense to put it in the UserInterfaceManager as Activities will most likely need an instance of it, which would create a circular dependency.
I also do not like the idea of the component being obtained from some kind of static method/property and injecting the activity from inside of it at the startup, as it creates duplicate lines of code in each activity.
Also, where do components and subcomponents fit in this kind of architecture? Why not create the separate
component for the data layer and expose just the repository and declare it as a dependency of the app component which would further isolate the details from abstraction? Maybe i should declare this component a dependcy of a service component which would enforce the layer architecure, as components can only use the types exposed in component interface? Or maybe i should use compoenent only when i need a custom scope and use the modules everywhere elsewhere?
I just overally think I am missing the bigger picture of the dagger. I will be really greatefull for answers, explanations and links to articles and other resouces that will let me understeand it better.
From the perspective of an Android developer, I fully understand your point. I asked myself this question too. The way how you construct an object in plain Java/Kotlin world is a little bit different. The main reason is due to the fact basic Android components (Activity/Fragment) don't allow constructor injection.
The answer to your question is, though, pretty straightforward. The Dagger Component is responsible for object creation, and you, as a developer, control what objects specific component provides. Let's use it in your scenario and provide some of the objects you might be interested in:
#Singleton
#Component(modules = [InMemoryPersistenceModule::class])
interface ApplicationComponent {
val service: Service
val anotherService: AnotherService
}
ApplicationComponent should be understood as a component for your whole application. It's not related to Android's Application class in any way. Now, you can simply create your component and let Dagger instantiate your objects:
val component = DaggerApplicationComponent.create()
val anotherService: AnotherService = component.anotherService
val service: AnotherService = component.service

Dealing with immutability in a GUI-based application

I have a GUI based application (Kotlin) that consists of a list of Notes to the left (it shows the note's title) and the selected Note's edition screen to the right.
When the users modify the title in the edition screen, the list item must show the new title.
In a mutable world, I'd do something like this:
interface Note {
fun title(): String
fun content(): String
fun setTitle(newTitle: String)
fun setContent(newTitle: String)
fun addListener(l: Listener)
}
class NoteListItem(n: Note) : Listener {
init {
n.addListener(this)
}
override fun onChange() { //from Listener
repaint();
}
fun repaint() {
//code
}
}
class NoteEditionScreen(n: Note) {
fun onTitleTextChanged(newTitle: String) {
n.setTitle(newTitle)
}
//...
}
Inside Note.setTitle method, listeners are notified.
Both "screens" have the same instance of Note, so changes are propagated.
However, with immutability:
interface Note {
fun title()
fun content()
fun title(newTitle: String) : Note
fun content(newContent: String) : Note
}
the method Note.title(String) returns a new instance of Note instead of changing the state.
In that case, how can I "notify" to the NoteListItem the title has changed?
Obviously, those two concepts don't go together nicely here.
The essence of the elements on your UI is: they allow for changes. The user doesn't know (or care) if the underlying object is immutable or not. He wants to change that title.
Thus, there are two options for you:
give up on your Node being immutable
introduce an abstraction layer that is used by your UI
In other words: you can add a mutable NodeContainer that is used for displaying immutable node objects on your UI.
Now you have to balance between having the advantages of keeping your Node class immutable and the disadvantages of adding mutable wrapper thingy around the Node class. But that is your decision; depending on your context.

Exception when using spring-data-mongodb with Kotlin

I'm new to Kotlin, and experimenting with spring-data-mongodb. Please see example below (also available here as fully runnable Maven project with in-memory MongoDb: https://github.com/danielsindahl/spring-boot-kotlin-example).
Application.kt
package dsitest
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
#SpringBootApplication
open class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
User.kt
package dsitest
import org.springframework.data.annotation.Id
import org.springframework.data.annotation.PersistenceConstructor
import org.springframework.data.mongodb.core.mapping.Document
#Document(collection = "user")
data class User #PersistenceConstructor constructor(#Id val id: String? = null, val userName: String)
UserRepository.kt
package dsitest
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, String>
KotlinIntegrationTest.kt
package dsitest
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit4.SpringRunner
#RunWith(SpringRunner::class)
#SpringBootTest
class KotlinIntegrationTest constructor () {
#Autowired
lateinit var userRepository : UserRepository;
#Test
fun persistenceTest() {
val user : User = User(userName = "Mary")
val savedUser = userRepository.save(user)
val loadedUser = userRepository.findOne(savedUser.id) // Failing code
println("loadedUser = ${loadedUser}")
}
}
When running the test KotlinIntegrationTest.persistenceTest, I get the following error message when trying to retrieve a User object from MongoDb:
org.springframework.data.mapping.model.MappingException: No property null found on entity class dsitest.User to bind constructor parameter to!
If I modify the User data class so that userName is nullable, everything works.
data class User #PersistenceConstructor constructor(#Id val id: String? = null,
val userName: String? = null)
I would like to understand why this is the case, since I don't want userName to be nullable. Is there some alternative, better way of defining my User class in Kotlin?
Many thanks,
Daniel
Yes, it is a known problem. You should check how the bytecode for your User class looks like. Java sees the constructor with all the parameters present and tries to call it with a null value for the 2nd one.
What you could do is to try adding #JvmOverloads to your constructor - this will force Kotlin compiler to generate all versions of the constructor and so the Spring Data Mongo could pick the correct one (get rid of the #PersistenceConstructor) then.
You could also define 1 constructor with no defaults - only for Java-based frameworks and 2nd one with some defaults your you. Or...
When I write things like you are now, I create simple 'persistence' data classes with no default values whatsoever that are mapped to/from my regular domain objects (a sort of abstraction over database). It may generate some overhead at the start - but keeping your domain model not coupled so tightly with the storage model is usually a good idea anyway.