Using koin in Multi-module application - android

Hi in a multi modules app, I am loading child modules using loadKoinModules() and unloading it using unloadKoinModules() in feature module my code looks like
class FeatureActivity:AppCompatActivity(){
private val loadFeatures by lazy { loadKoinModules(featureModule) }
private fun injectFeatures() = loadFeatures
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injectFeatures()
}
override fun onDestroy() {
super.onDestroy()
unloadKoinModules(featureModule)
}
}
Everything works fine but problem start when another instance on same activity is loaded. While current activity is in background. App crash due to error below
org.koin.error.BeanOverrideException: Try to override definition with Factory
Is there a way to avoid this error

It is somehow correct what you are doing, you can unload dynamically as you do this is why unloadKoinModules has been added link
but why aren't you unloading onStop? according to android lifecycle and what you want to do, you have to unload in onStop
When activity gets focus onCreate will occur (and you will load modules), later when activity loses focus, onStop will occurs (and you will unload modules) and the circle between the events...
class FeatureActivity:AppCompatActivity(){
private val loadFeatures by lazy { loadKoinModules(featureModule) }
private fun injectFeatures() = loadFeatures
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injectFeatures()
}
override fun onStop() {
super.onStop()
unloadKoinModules(featureModule)
}
}

Koin won't let to you redefine an already existing definition (type,name,path …​). You will run into an error.
You need to allow definition override :-
val featureModule = module {
// override for this definition
single<yourType>(override=true) { YourClass() }
}
ALSO you can override on module level instead of overriding on definition level only:-
val featureModule = module(override=true) {
single<yourType> { YourClass() }
}
Important:-
Order matters when listing modules and overriding definitions. You must have your overriding definitions in last of your module list.

Some possibilities:
Load your feature module in the top-level application level and don't scope it to any activity lifecycle.
Add a reference-counting wrapper around your module load/unload so the module is not reloaded if it is already loaded, and it is only unloaded when the usage count is zero. (You can simplify this by not caring about unloading and change the count to just a "initialised" boolean.)

Related

Make Retrofit API call in Activity using Kotlin coroutines

I want to load some data inside activity after the button is clicked. I came up with the following solution and it works as I expect. But I just started learning kotlin coroutines and I want someone else to comment on my code. For example, is it okay that I update the UI using lifecycleScope.launch? I could probably use withContext(Dispatchers.Main) instead but is there a difference?
Is my implementation good in general? Is there something that could be optimzed/refactored?
I understand that it's better to use ViewModel and make API calls there but in this case I want all action to happen inside the activity.
class MainActivity : AppCompatActivity() {
var apiCallScope: CoroutineScope? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.btn_load_content).setOnClickListener {
// Cancel previous API call triggered by the click.
// I don't want to have multiple API calls executing at the same time.
apiCallScope?.cancel()
showProgress(true)
apiCallScope = CoroutineScope(Dispatchers.IO)
apiCallScope!!.launch {
// Execute Retrofit API call
val content = api.loadContent().await()
// Update UI with the content from API call on main thread
lifecycleScope.launch {
showProgress(false)
drawContent(content)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
apiCallScope?.cancel()
}
private fun showProgress(show: Boolean) {
// TODO implement
}
private fun drawContent(content: String) {
// TODO implement
}
}
It's preferable to use ViewModel to make such types of operations and not perform them inside Activity, especially in the onCreate method.
ViewModel gives you the viewModelScope property, any coroutine launched in this scope is automatically canceled if the ViewModel is cleared to avoid consuming resources.

How can I make interface methods internal with Kotlin?

I wrote a simple SwipeLayout library. My goal is to publish it via GitHub for everyone, including myself, to use.
I encountered a problem when I decided to encapsulate some methods. I was hoping for an internal modifier to kick in, and solve the problem, but I can't find a proper way to do it.
Here are some code and explanation:
class SwipeLayout (...) : FrameLayout(...),
BackgroundViewsVisibilityController, ... {
private val backgroundController = BackgroundController(
this //(BackgroundViewsVisibilityController)
)
override fun revealLeftUnderView() {...}
override fun hideLeftUnderView() {...}
override fun revealRightUnderView() {...}
override fun hideRightUnderView() {...}
}
interface BackgroundViewsVisibilityController {
fun revealLeftUnderView()
fun revealRightUnderView()
fun hideLeftUnderView()
fun hideRightUnderView()
}
These are the methods to hide from users. How can I best achieve it?
internal tells the compiler that this interface, class or function can only be called from the current module. When you release a library, that internal interface can not be called (without using reflection) from your library.
You want to make SwipeLayout public (for third party usage) so you can not make the interface BackgroundViewsVisibilityController internal at the same time, because your class is extending that interface.
If you really want that clients can not call the functions from your interface, consider not having an interface (as an interface is usually there for a caller to be used) but making those functions private in SwipeLayout:
class SwipeLayout (...) : FrameLayout(...)
private fun revealLeftUnderView() {...}
private fun hideLeftUnderView() {...}
private fun revealRightUnderView() {...}
private fun hideRightUnderView() {...}
}
More information about Kotlin Visibility Modifiers

Reference activity in koin Module

I have a single activity application.
My MainActivity is referenced in a number of dependency injection modules, as the implementer of these interfaces. I currently have a work around, which is less than ideal.
class MainActivity : TransaktActivity(), RegistrationNavigator, IAuthPresenter,
IAuthButtonNavigator {
override fun navigateAwayFromAuth() {
navController.navigate(R.id.homeFragment)
}
override fun navigateToAuthPin(buttonId: Int) {
//todo navigate to auth with pin fragment
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_mainActivity = this
setContentView(R.layout.activity_main)
}
companion object {
private var _mainActivity: MainActivity? = null
fun getInstance() = _mainActivity
}
}
interface RegistrationNavigator {
fun navigateToCardDetails()
fun navigateToOtpCapture()
fun navigateToLoading()
fun navigateOutOfCardRegistration()
}
The appModule is a Koin Module
val appModule = module {
viewModel { SharedViewModel() }
single { MainActivity.getInstance() as RegistrationNavigator }
}
What is the preferred way of achieving this?
Android-lifecycled components such as activities should not be in koin modules.
For example you will have issues with e.g. configuration changes since the koin module would be serving references to stale activity after the activity is recreated.
I haven't really worked with NavController but rather rolled up my own navigation solution. As a generic approach I would refactor the RegistrationNavigator implementation to a separate class the instance of which you can provide from your koin module. If lifecycle-dependent params such as Context (or NavController) are needed, supply them as function args.

Why not show data when use Dagger2 on Android

In my application i want use Dagger2 and i want show just one image from server and for show image i used Picasso.
I write below codes, but after run application not show me any image into imageview!
For android i use Kotlin language.
Application class :
class App : Application() {
var component: AppComponent? = null
override fun onCreate() {
super.onCreate()
//Init dagger component
component = DaggerAppComponent.builder().modulePicasso(ModulePicasso(this)).build()
}
fun getAppComponent(): AppComponent? {
return component
}
}
Activity class :
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
App().getAppComponent()?.getPicasso()?.load("https://cdn01.zoomit.ir/2017/6/5f0e97c1-9eb7-4176-aa02-35252489ede8.jpg")
?.into(imageView)
}
}
How can i fix it?
I think it's because you are creating a new instance of you application class, eg. App() and then calling getAppComponent() which for sure returns null, as you should not construct the application instance yourself, but instead access a static property referencing it.
To fix it, you need to add a static property (instance) to the App class and get the AppComponent using that property.

onCleared is not being called on Fragment's attached ViewModel

I stumbled on a problem when ViewModel.OnCleared() is not being called when the app goes to background (even if Don't keep activities is enabled) but I can see that Fragment.onDestroy() is actually being called.
What could be wrong in the following code? How can I make ViewModel.OnCleared() to be actually called in this scenario?
ViewModel:
class ViewModelFirst(application: Application) : AndroidViewModel(application) {
companion object {
private const val TAG = "ViewModelFirst"
}
init {
Log.v(TAG, "Created")
}
override fun onCleared() {
super.onCleared()
Log.v(TAG, "onCleared")
}
}
Fragment:
class FragmentFirst : Fragment() {
companion object {
private const val TAG = "FragmentFirst"
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
ViewModelProviders.of(this).get(ViewModelFirst::class.java)
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onDestroy() {
super.onDestroy()
Log.v(TAG, "onDestroy")
}
}
Activity:
class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction().replace(R.id.container, FragmentFirst()).commit()
}
}
override fun onDestroy() {
super.onDestroy()
Log.v(TAG, "onDestroy")
}
}
Answer myself:
It's a bug of com.android.support:appcompat-v7:27.1.0
I am experiencing this issue if I use the following dependencies:
implementation 'com.android.support:appcompat-v7:27.1.0'
implementation "android.arch.lifecycle:extensions:1.1.0"
If I change the version of appcompat-v7 27.1.0 -> 27.0.2, then ViewModel.OnCleared()works as expected (I have the call when the app goes to background).
appcompat-v7:28.0.0-alpha1 works as well, looks like this is a problem only of appcompat-v7:27.1.0
Update (June 2018)
As #Akshay said, the bug was kinda fixed on 27.1.1. But not fully unfortunately.
The following scenario is still unfixed:
Have Don't keep activities enabled.
Start the app.
Push home button.
On 27.0.2 I have the following output at logcat:
V/ViewModelFirst: Created
V/ViewModelFirst: onCleared
V/FragmentFirst: onDestroy
V/MainActivity: onDestroy
Which totally correct.
But on 27.1.1 till 28.0.0-alpha3 I have the following output at logcat:
V/ViewModelFirst: Created
V/FragmentFirst: onDestroy
V/MainActivity: onDestroy
As we can see activity and fragment was destroyed but viewModel was not notified with onCleared.
I suspect that in case if the Don't keep activities will be disabled and the app at background will be naturally unloaded by Android (due another app claiming for a bunch of resources) at some moment of time the viewModel.onCleared() will not be called which is very sad.
P.S. I have pushed the code here: https://github.com/allco/onClearedInvestigation
And have reported the issue to Google here: https://issuetracker.google.com/issues/110285295
Update (August 2018)
28.0.0-rc01 has this problem solved. Yay!
It was an issue in Support Library v27.1.0 and has been fixed in Support Library v27.1.1.
There is a bug reported previously at https://issuetracker.google.com/issues/74139250.
Refer this link for more details: https://developer.android.com/topic/libraries/support-library/revisions

Categories

Resources