What is the proper way to provide Context to a DataSource with Clean Architecture, MVVM and Koin?
Asked Answered
A

1

8

I am developing an Android application with Kotlin in which I need to get the current location of the mobile device. I've already found a way to do it in various examples, but I don't know how to integrate this logic according to Clean Architecture with MVVM.

In my architecture I have the following layers: Presentation, UseCase, Data, Domain and Framework. I have the presentation layer organized with the MVVM pattern. Also I use Koin for dependency injection.

I get all the data that my application needs from the DataSources that are in the Framework layer. For example, data obtained remotely or from a database, or data provided by the device (location).

Here is an example of the files involved in obtaining the location from the ViewModel:

ConfigurationViewModel (Presentation layer):

class ConfigurationViewModel(private val useCase: GetLocationUseCase) : ViewModel() {

    fun onSearchLocationButtonClicked() = liveData<Resource<Location>>(Dispatchers.IO) {
        emit(Resource.loading())
        try {
            emit(Resource.success(data = useCase.invoke(UseCase.None())))
        } catch (exception: Exception) {
            emit(Resource.error(message = exception.message))
        }
    }

GetLocationUseCase (Usecase layer):

class GetLocationUseCase(private val locationRepository: LocationRepository) :
    UseCase<Location, UseCase.None>() {

    override suspend fun invoke(params: None): Location = locationRepository.getLocation()
}

LocationRepositoryImpl (Data layer):

class LocationRepositoryImpl(private val locationDeviceDataSource: LocationDeviceDataSource) :
    LocationRepository {
    override suspend fun getLocation(): Location = locationDeviceDataSource.getLocation()
}

LocationDeviceDataSourceImpl (Framework layer):

class LocationDeviceDataSourceImpl(private val context: Context) : LocationDeviceDataSource {

    override suspend fun getLocation(): Location =
        LocationServices.getFusedLocationProviderClient(context).lastLocation.await()
}

As you can see, in LocationDeviceDataSourceImpl I need the context to get the last location. I don't know what is the best way to provide the context to the DataSource. I have seen several examples but I want to understand what is the best way to do it.

I have seen the following options:

  • Use AndroidViewModel, to provide the context of the application to the UseCase, to provide it to the Repository to finally provide it to the DataSource. But I am not sure if it is a suitable way, if it is safe and if it maintains the sense of architecture. Based on Alex's answer

  • The other option that I have seen is to inject the androidContext through Koin to the DataSource, which is another way to provide the context of the application. In this way it would not be necessary for the context to go through the ViewModel, the UseCase or the Repository. Based on maslick's answer

What would be the appropriate way to integrate this logic according to my architecture and why? Or is this problem because of my architecture?

Thanks a lot for your time.

Accent answered 4/5, 2020 at 14:12 Comment(2)
Some some di (/ service locator) library like dagger or koin. This will simplify your app code a lot. You don't have to pass some dependency from fragment all the way to the data sourceGerontology
@sonnet Yes, I am currently using what you are saying. I'm injecting the context to the DataSource using Koin. I think it is a better option than the AndroidViewModel. But I wanted to know if this was a good option or if there was a better one. Thank you very much for your answer.Accent
G
1

The correct way is not to pass context there at all. LocationDeviceDataSourceImpl should not know about context at all. It should know about the FusedLocationProviderClient though - so pass the location client already prepared to the LocationDeviceDataSourceImpl.

And as FusedLocationProvider should know about Context since it cannot exist without it - have it declared and initialized somewhere where you declare(or inject) all the context-dependent stuff. The rule of thumb here is to use context only where it is required for the object's existence.

I usually have a module that contains context and provides it to all the Android System services that require it, and I provide those objects to all the other modules where they are needed, thus excluding context passing whatsoever.

Guidry answered 23/2, 2023 at 9:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.