I try to create scope for a feature. I define a module like this.
val appModule = module {
scope(named("ARTIST_SCOPE")) {
scoped {
ArtistRepository(get())
}
scoped {
GetArtistsUseCase(get())
}
viewModel { ArtistViewModel(get()) }
}
}
My goal is to make ArtistRepository, GetArtistUseCase, and ArtistViewModel only accessible inside Artist Feature.
In my activity
class ArtistActivity : AppCompatActivity() {
private val artistScope = getKoin().createScope("artistScope", named("ARTIST_SCOPE"))
private val viewModel: ArtistViewModel by artistScope.viewModel(this)
...
}
My problem is when I leave this activity and return back to it.
I got this error.
org.koin.core.error.ScopeAlreadyCreatedException: A scope with id 'artistScope' already exists. Reuse or close it.
enter code here
How to reuse the existing scope?
or Am I implement the scope in the right way?
You want to use getOrCreateScope(). This will get an existing instance if you have one that isn't closed with the same scopeId, or it will create a new instance if it needs to.
Related
I have an android app (main) that creates an object -- in a singleton manner and is used throughout the application. The class is called NetworkFrame which is a common module in the project.
The main app has an activity that contains a fragment which exists in another module in the project (viewer). The viewer module is imported into the main app using Gradle. I need to pass the NetworkFrame object to the viewer fragment.
Both the module and the app both have NetworkFrame as a dependency.
The object is too detailed to use serializable or Parcel. I have looked into dependency injection, but I'm not sure if that's the correct use of it.
class MainApplication : Application() {
companion object {
var networkFrame: NetworkFrame? = null
}
}
class MainFragment : Fragment() {
...
if (MainApplication.networkFrame == null) {
MainApplication.networkFrame = networkFrame
}
viewModel.networkFrame = MainApplication.networkFrame
...
}
Above shows the creation of the object in the main application. I need to pass this object to the viewer fragment's networkFrame object.
class ViewerModel : ViewModel() {
var networkFrame: NetworkFrame? = null
}
What's the best way to get this object to the viewer fragment?
I think that you require an instance of NetworkFrame then I recommend to you create an interface that provides to you the instance
interface HasNetworkFrame {
fun getNetworkFrame(): NetworkFrame
}
This interface should be implemented on the application, something like this
class OneApplication : Application(), HasNetworkFrame{
fun getNetworkFrame(): NetworkFrame {
// return your object
}
}
And in your fragment can access via something like this
val frame = (requireContext().applicationContext as? HasNetworkFrame)?.getNetworkFrame()
However i think that this is not a good approach, take a look into solid principles and put focus on
Dependency Inversion Principle
Was testing the following code. For brevity:
Class ActivityA {
Val aViewModel: AViewModel by viewModel()
Fun onCreate(){
val id = ….
getKoin().setProperty(“id’”, id)
loadKoinModules(aModule)
}
}
And in my modules.kt definitions:
Val aModule = modules {
viewModel { AViewModel(getProperty(“id”))} //works
}
//For ActivityB
Val bModule = modules {
viewModel { BViewModel(getProperty(“id”)} // Caused by: org.koin.core.error.MissingPropertyException: Property 'Id' not found
}
Why do I get this error when trying to create the BViewModel instance. I would have thought that getKoin() would be the same Koin Instance retrieved. But it seems to work only within the same Module definition loaded. I can’t get the property ‘id’ for module B. I have to do the same in ActivityA’s onCreate() for ActivityB.
Any explanation or links regarding this would be appreciated or do I have to use something like Koins' Scope feature for this?
Many Thanks
In Arrow Kt Documentation on Dependency Injection, the dependency is defined at the "Edge of the World" or in Android could be an Activity or a Fragment. So the given example is as follow:
import Api.*
class SettingsActivity: Activity {
val deps = FetcherDependencies(Either.monadError(), ActivityApiService(this))
override fun onResume() {
val id = deps.createId("1234")
user.text =
id.fix().map { it.toString() }.getOrElse { "" }
friends.text =
deps.getUserFriends(id).fix().getOrElse { emptyList() }.joinToString()
}
}
But now I'm thinking how could the SettingsActivity in the example could be unit tested? Since the dependency is created within the activity, it could no longer be changed for testing?
When using some other Dependency Injection library, this dependency definition is create outside of the class it will be used on. For example in Dagger, a Module class is created to define how the objects (dependencies) are created and an #Inject is used to "inject" the dependency defined inside the module. So now when unit testing the Activity, I just have to define a different module or manually set the value of the dependency to a mock object.
In Dagger you would create a Mock or Test class that you would #Inject instead of ActivityApiService. It is the same here.
Instead of:
class ActivityApiService(val ctx: Context) {
fun createId(): String = doOtherThing(ctx)
}
You do
interface ActivityApiService {
fun createId(): String
}
and now you have 2 implementations, one for prod
class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
override fun createId(): Unit = doOtherThing(ctx)
}
and another for testing
fun testBla() {
val api = object: ActivityApiService {
override fun createId(): String = "4321"
}
val deps = FetcherDependencies(Either.monadError(), api)
deps.createId("1234") shouldBe "4321"
}
or even use Mockito or a similar tool to create an ActivityApiService.
I have a couple of articles on how to decouple and unitest outside the Android framework that aren't Arrow-related. Check 'Headless development in Fully Reactive Apps' and the related project https://github.com/pakoito/FunctionalAndroidReference.
If your dependency graph becomes too entangled and you'd like some compile-time magic to create those dependencies, you can always create a local class in tests and #Inject the constructor there. The point is to decouple from things that aren't unitestable, like the whole Android framework :D
I am migrating an application to MVVM and clean architecture, and I am missing one part of the puzzle.
The problem domain:
List all applications on device and display them in the Fragment / Activity
A device app is represented by its package name:
data class DeviceApp(val packageName: String)
This is how the device apps are listed:
private fun listAllApplications(context: Context): List<DeviceApp> {
val ans = mutableListOf<DeviceApp>()
val packageManager: PackageManager = context.applicationContext.packageManager
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
val packageName = applicationInfo.packageName
ans.add(DeviceApp(packageName))
}
return ans
}
As I understand, calling listAllApplications() should be done in a UseCase inside the 'Domain Layer', which is called by a ViewModel.
However listAllApplications receives a Context, and the Domain Layer should be plain code only.
In clean architecture / MVVM, in which layer should I put listAllApplications(context)?
And more generally, how should the ViewModel interact with Android framework APIs that require Context (location, etc.)?
Domain Layer should be plain code only.
That's correct!, but in my opinion it's partially correct. Now considering your scenario you need context at domain level. You shouldn't have context at domain level but in your need you should either choose other architecture pattern or consider it as exceptional case that you're doing this.
Considering you're using context at domain, you should always use applicationContext in spite of activity context, because earlier persists through out process.
How should the ViewModel interact with android framework APIs that require Context (location, etc.)?
Whenever you need Context at ViewModel either you can provide it from UI as method parameter (I.e. viewModel.getLocation(context)) or else use AndroidViewModel as your parent class for ViewModel (it provides getApplication() public method to access context through out ViewModel).
All I would like to point you out is that make sure you don't accidentally hold any View/Context globally inside ViewModel/Domain Layer, because it can make catastrophe like memory leaking or crashes at worse.
You can solve this problem very cleanly with dependency-injection. If you aren't already using DI, you probably want to be, as it will greatly simplify your clean-architecture endeavours.
Here's how I'd do this with Koin for DI.
First, convert your usecase from a function to a class. This allows for constructor injection:
class ListAllApplications(private val context: Context) {
...
}
You now have a reference to context inside your usecase. Great! We'll deal with actually providing the value of context in a moment.
Now you're thinking... but aren't usecases meant to use reusable functions? What's the guy on about with usecases being classes?
We can leverage the miracle that is operator funs to help us here.
class ListAllApplications(private val context: Context) {
operator fun invoke(): List<DeviceApp> {
val ans = mutableListOf<DeviceApp>()
val packageManager: PackageManager = context.applicationContext.packageManager
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
val packageName = applicationInfo.packageName
ans.add(DeviceApp(packageName))
}
return ans
}
}
invoke is a special function which allows an instance of a class to be invoked as if it were a function. It effectively transforms our class into a function with an injectable constructor 🤯
And this allows us to continue to invoke our usecase in the ViewModel with the standard function invocation syntax:
class MyViewModel(private val listAllApplications: ListAllApplications): ViewModel {
init {
val res = listAllApplications()
}
}
Notice that our ListAllApplications usecase is also being injected into the constructor of MyViewModel, meaning that the ViewModel remains entirely unaware of Context.
The final piece of the puzzle is wiring all this injection together with Koin:
object KoinModule {
private val useCases = module {
single { ListAllApplications(androidContext()) }
}
private val viewModels = module {
viewModel { MyViewModel(get()) }
}
}
Don't worry if you've never used Koin before, other DI libraries will let you do similar things. The key is that your ListAllApplications instance is being constructed by Koin, which provides an instance of Context with androidContext(). Your MyViewModel instance is also being constructed by Koin, which provides the instance of ListAllApplications with get().
Finally you inject MyViewModel into the Activity/Fragment which uses it. With Koin that's as simple as:
class MyFragment : Fragment {
private val viewModel: MyViewModel by viewModel()
}
Et Voilà!
I have an interface WordsDataSource using which I have implemented two concrete classes namely WordsLocalDataSource that deals with local database and another WordsRemoteDataSource that deals with manipulating data online on the server. The problem is when I try to inject the two classes in repository class using abstract class name WordsDataSource like
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsDataSource,
private val wordsLocalDataSource: WordsDataSource) {
And adding dependencies in Application class like
class WordsApplication : Application(), KodeinAware {
override val kodein = Kodein.lazy {
import(androidXModule(this#WordsApplication))
bind() from singleton { WordsDatabase.getInstance(instance()) }
bind<WordsDao>() with singleton { instance<WordsDatabase>().wordsDao() }
bind() from singleton { WordsLocalDataSource(instance()) }
bind() from singleton { WordsRemoteDataSource() }
bind<WordsRepository>() with singleton { DefaultWordsRepository(instance(), instance()) }
bind() from provider { ViewModelFactory(instance()) }
}
Then upon running the app I encounter the following issue in the logcat
org.kodein.di.Kodein$NotFoundException: 2 bindings found that match bind<WordsDataSource>() with ?<WordsFragment>().? { ? }:
bind<WordsLocalDataSource>() with singleton { WordsLocalDataSource }
bind<WordsRemoteDataSource>() with singleton { WordsRemoteDataSource }
I have tried the workaround for this by simply declaring the variables by their respective concrete class names like
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsRemoteDataSource,
private val wordsLocalDataSource: WordsLocalDataSource) {
But still want to know whether or not is there any way to resolve the issue.
I am using the following dependencies for kodein
implementation "org.kodein.di:kodein-di-generic-jvm:6.3.3"
implementation "org.kodein.di:kodein-di-framework-android-x:6.3.3"
You have done it the right way by writing the explicit types:
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsRemoteDataSource,
private val wordsLocalDataSource: WordsLocalDataSource)
When working with sub-types we cannot know what kind of implementation to choose. Writing
DefaultWordsRepository(
private val wordsRemoteDataSource: WordsDataSource,
private val wordsLocalDataSource: WordsDataSource)
Doesn't cannot find if you want both sub-types or twice the WordsRemoteDataSource or WordsLocalDataSource. Thus, you need to explicit define your types. Even, we could put WordsRemoteDataSource in the property wordsLocalDataSource, as we cannot rely on variable names.