Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject Activity #49

Closed
stanete opened this issue Feb 12, 2018 · 30 comments
Closed

Inject Activity #49

stanete opened this issue Feb 12, 2018 · 30 comments
Milestone

Comments

@stanete
Copy link

stanete commented Feb 12, 2018

Hi,

Koin is awesome but I'm having an issue. Some libraries, like Firebase Analytics, depend on the current Activity to perform some actions like setting a current screen.

class Analytics(private val activity: Activity) {

  private val firebaseAnalytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(activity)

  fun setCurrentScreen(screenName: String) {
    firebaseAnalytics.setCurrentScreen(activity, screenName, null)
  }
}
class MainPresenter(analytics: Analytics) { 
    
}
class MainActivity : AppCompatActivity() {

  val presenter : MainPresenter by inject()

}

Is this possible with Koin? If yes, then I don't see how.

@stanete stanete changed the title Inject Activity in android app Inject Activity Feb 12, 2018
@arnaudgiuliani
Copy link
Member

Hello,

I'm currently writing recipes for such use. You can bind your Activity in your activity :

  • activity property in your presenter must be lateinit var - to allow the value to be set later
  • Set your activity directly in your presenter when need (in your MainActivity)
class Analytics() {
  lateinit var activity: Activity
  private val firebaseAnalytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(activity)

  fun setCurrentScreen(screenName: String) {
    firebaseAnalytics.setCurrentScreen(activity, screenName, null)
  }
}
class MainActivity : AppCompatActivity() {

  val presenter : MainPresenter by inject()

    override fun onStart() {
        super.onStart()
        presenter.activity = this
    }
}

@stanete
Copy link
Author

stanete commented Feb 12, 2018

Thank you for your quick response. The issue is that I don't want my presenter to depend on any activity. The only class who should know about the activity is Analytics which is the one who needs it. How can I achieve that with Koin?

I would like to avoid doing something like this:

class MainActivity : AppCompatActivity() {

  val presenter : MainPresenter by inject()

    override fun onStart() {
        super.onStart()
        presenter.analytics.activity = this
    }
}

@arnaudgiuliani
Copy link
Member

arnaudgiuliani commented Feb 13, 2018

Ok. Then, if you don't want to keep in in your presenter, pass it through function parameter?

class MainPresenter(analytics: Analytics) { 
  
  fun doSomething(context : Context){
  // ... use your activity here
  }  

}

Activity and all Android components are somewhat "outside" of Koin. The only thing that we could do, is about Fragment instance (because they are created manually).

@stanete
Copy link
Author

stanete commented Feb 13, 2018

Hmm I understand. So there is no nice way to provide an Activity to a Koin module. I don't know how Koin really works underneath yet, I'll have to study it. But it should be nice if we could develop a nice Koin solution to this. A lot of external frameworks depend on some Android component (like the Activity in the case of Firebase). Do you see it possible?

@fredy-mederos
Copy link
Contributor

Maybe a curren-activity-provider could me implemented using ActivityLifecycleCallbacks
It is just a thought, I will try it later.

@arnaudgiuliani
Copy link
Member

Interesting idea. Don't know if it's in the scope of Koin to provide such thing, or just a recipe to help about such situation.

@arnaudgiuliani
Copy link
Member

I think this has to be a "side" solution to implement if you want to keep an activity under the hand. This solution has to be documented for online reference.

@Bodo1981
Copy link

I came accross the same issue to inject android framework components (activity, fragment, ...) in some dependencies. so it would be great is koin can support this. if you do not want to include it in the core koin project how about adding it to the android module?

in the meanwhile has someone a solution other than the already mentioned?

btw GREAT LIBRARY!!!

@vpotvin
Copy link

vpotvin commented Feb 19, 2018

Similar issue trying to inject an instance of an Anko SQLite Database Helper. Would be amazing if KOIN could be Android Context-aware somehow to facilitate these kinds of dependencies.

@arnaudgiuliani
Copy link
Member

If I understand, could be nice to allow inject current Activity, right?

given class:

class Analytics(val activity: Activity)

we could the a Koin module with currentActivity() which resolves current Activity:

val module = applicationContext{
    factory { Analytics(currentActivity())}
}

this way, each time you ask for Analytics instance, you will resolve it with current Activity.

@Bodo1981
Copy link

That will be the most used case with activity.

But maybe koin could provide a more generic way to inject also other classes

val module = applicationContext {
    factory { Analytics(currentContext<GenericType>()) }
}

The provided (GenericType) class is the class where your Analytics class will be injected, e.g.

class AnalyticsActivity : Activity, Navigator {
    val analytics: Analytics by inject() // in this case GenericType can be (Analytics-)Activity or Navigator
}
class AnalyticsFragment : Fragment, Navigator {
    val analytics: Analytics by inject() // in this case GenericType can be (Analytics-)Fragment or Navigator
}

Can this be achieved with koin?

@sho5nn
Copy link

sho5nn commented Feb 20, 2018

In Dagger, If want depend to Activity, At the timing of injecting Presenter, providing an instance of Activity to inject() and solve it.

The javadoc of AndroidInjector<T>.Builder#seedInstance(T) may be helpful.
https://github.com/google/dagger/blob/master/java/dagger/android/AndroidInjector.java

@Bodo1981
Copy link

Great idea!
Maybe we could add an optional parameter (varargs or single value) to the inject delegate to provide additional context related objects

class AnalyticsActivity : Activity, Navigator {
    val analytics: Analytics by inject(this, this as Navigator, ...) // pass additional optional parameters to the inject delegate
}

@arnaudgiuliani
Copy link
Member

I 've add Koin parameters to release 0.9.0.

you will be able to use parameters in your definition. Given class:

class MyPresenter(val activity : MyActivity)

We can use parameters to be injected with by inject()

val module = applicationContext {
   factory { params -> MyPresenter(params["activity"])}
}

Injecting the parameter:

class MyActivity : AppCompatActivity(){
   
   // Ask for MyPresenter injection and provide parameters
   val presenter : MyPresenter by inject( parameters = mapOf("activity" to this))
}

stay tuned👍

@Bodo1981
Copy link

Great to hear!

Any timetable when 0.9.0 will be released?

@arnaudgiuliani
Copy link
Member

release 0.9.0 is scheduled for end of this week or next week.

@fredy-mederos
Copy link
Contributor

"parameters" feature will be available for viewModels too ???
I noticed in v0.9.0-rc1 viewModels are being created with emptyMap of parameters.
Thanks

@arnaudgiuliani
Copy link
Member

arnaudgiuliani commented Feb 27, 2018 via email

@westonal
Copy link

Is this still the best way to achieve this?

I made a little helper:

inline fun <reified T> Activity.injectActivity(): Lazy<T> =
    inject(parameters = { mapOf("activity" to this) })
class MyActivity : AppCompatActivity(){
   
   // Ask for MyPresenter injection and provide parameters
   val presenter : MyPresenter by injectActivity()
}

@dgngulcan
Copy link

I am using parameters like below @westonal.

LocationModule.class

factory { (activity: MainActivity) -> FusedLocationProviderClient(activity) }

MainActivity.class

val provider: FusedLocationProviderClient by inject { parametersOf(this@MainActivity) }

@arnaudgiuliani
Copy link
Member

Yes, injection parameters are the best in that case!

@Dmitry-Borodin
Copy link

Dmitry-Borodin commented Mar 17, 2019

Is there a way to create dependencies for scopes?
Passing data around with custom strings optional parameters looks weird, is there a way to pass scope-local dependencies to create scope, and use it for dependencies inside this scope?

I have similar usecase as above, but activity injects presenter, that depends on LocationProvider, that uses FusedLocationClient, requiring activity. So Activity should be part of the scope, not part of specific inject.

@kassim
Copy link

kassim commented Jul 2, 2019

^ agree, it would be nice if we could start a scope with a number of module params, in the same way we call startKoin()

@GorkemKarayel
Copy link

@dgngulcan thx

@syt0r
Copy link

syt0r commented Sep 10, 2019

How can the initial situation be solved? I have a similar situation: there is Interactor that needs activity reference and I'm injecting this interactor into viewModel constructor, but this way I can't use custom arguments.

BillingInteractor(activity: Activity) {
...
}

MyViewModel(private val billingInteractor: BillingInteractor) : ViewModel() {
...
}

@bkoruznjak
Copy link

@SYtor you could do something like:

fun provideYourModule() = module {
    viewModel { (activity: Activity) -> 
        MyViewModel(billingInteractor = BillingInteractor(activity))
    }
}

and in your activity:

private val myViewModel: MyViewModel by viewModel { parametersOf(this@Activity)}

@makaroffandrey
Copy link

@bkoruznjak
Please be aware that your solution may cause your activity to leak. For example if the screen was rotated. In this case, an Activity will be recreated but the new Activity will get the same instance of MyViewModel, which still holds a reference to the old Activity which will prevent it from being garbage collected.

@moizalidv
Copy link

@makaroffandrey makes a good point. @arnaudgiuliani any suggestion to avoid leaks?

@makaroffandrey
Copy link

@moizalidv
I ended up creating an extension function that creates a custom scope that is tied to the Activity and has the Activity itself in the dependency tree. This allows for the injection of Activity-dependent components without leaks.
#428 (comment)

It's a shame that Koin cannot do something like that out of the box.

@Oleksandr32
Copy link

You can do this with scope.
For that your activity must extends ScopeActivity
For example, class MainActivity : ScopeActivity()

My example is for MainNavigation:

class MainNavigationImpl(private val navController: NavController) : MainNavigation

my module will looks like:

val presentationModule = module {

    scope<MainActivity> {
        scoped<MainNavigation> {
            val navController = get<MainActivity>().findNavController(R.id.root_navigation_host_fragment)
            MainNavigationImpl(navController)
        }
    }
    
    scope<ChatsFragment> {
        scoped { ChatsPresenter(get()) }
    }
}

then I can easily use my dependency (for example, MainNavigation):

class ChatsPresenter(private val navigation: MainNavigation)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests