In our "SearchUsecase" we have access to "ShowFtsDao" directly.
Does it violate the Clean Architecture principles? Does it violate the MVVM architecture?
Assuming our intention is to develop a well-built, standard structure, is there anything wrong with this piece of code?
class SearchUsecase #Inject constructor(
private val searchRepository: SearchRepository,
private val showFtsDao: ShowFtsDao,
private val dispatchers: AppCoroutineDispatchers
) : SuspendingWorkInteractor<SearchShows.Params, List<ShowDetailed>>() {
override suspend fun doWork(params: Params): List<ShowDetailed> {
return withContext(dispatchers.io) {
val remoteResults = searchRepository.search(params.query)
if (remoteResults.isNotEmpty()) {
remoteResults
} else {
when {
params.query.isNotBlank() -> showFtsDao.search("*$params.query*")
else -> emptyList()
}
}
}
}
data class Params(val query: String)
}
I believe your use case handles more logic than it needs to.
As a simple explanation I like to think about the components this way:
Sources: RemoteSource (networking), LocalSource (db), optionally MemorySource are an abstraction over your database and networking api and they do the IO thread switching & data mapping (which comes in handy on big projects, where the backend is not exactly mobile driven)
Repository: communicates with the sources, he is responsible for deciding where do you get the data from. I believe in your case if the RemoteSource returns empty data, then you get it from the LocalSource. (you can expose of course different methods like get() or fetch(), where the consumer specifies if it wants the latest data and based on that the repository calls the correct Source.
UseCases: Talk with multiple repositories and combine their data.
Yes, it does.
Because your domain module has access to the data module and actually you've violated the dependency rule.
That rule specifies that something declared in an outer circle must
not be mentioned in the code by an inner circle.
Domain layer must contain interfaces for details (Repositories) which are implemented in the data layer,
and then, they could be injected into the UseCases (DIP).
Related
Suppose you have a list of users downloaded from a remote data source in your Android application, and for some reason you do not have a local DB. This list of users is then used throughout your entire application in multiple ViewModels to make other network requests, so you would surely like to have it cached for as long as the app lives and re-fetch it only on demand. This necessarily means you want to cache it inside the Data Layer, which is a Repository in my case, to then get it from your ViewModels.
It is easy to do in a state holder like a ViewModel - just make a StateFlow or whatever. But what if we want a Flow of List<User> (that is cached in RAM after every API request) available inside a repository to then collect from it from the UI Layer? What is the most testable, stable and right way of achieving this?
My initial idea led to this:
class UsersRepository #Inject constructor(
private val usersApi: UsersApi,
private val handler: ResponseHandler
) {
private val _usersFlow = MutableStateFlow<Resource<List<UserResponse>>>(Resource.Empty)
val usersFlow = _usersFlow.asStateFlow()
suspend fun fetchUserList() = withContext(Dispatchers.IO) {
_usersFlow.emit(Resource.Loading)
_usersFlow.emit(
handler {
usersApi.getUsers()
}
)
}
}
Where ResponseHandler is:
class ResponseHandler {
suspend operator fun <T> invoke(block: suspend () -> T) = try {
Resource.Success(block())
} catch (e: Exception) {
Log.e(javaClass.name, e.toString())
val errorCode = when (e) {
is HttpException -> e.code()
is SocketTimeoutException -> ErrorCodes.SocketTimeOut.code
is UnknownHostException -> ErrorCodes.UnknownHost.code
else -> Int.MAX_VALUE
}
Resource.Error(getErrorMessage(errorCode))
}
}
But while researching I found random guy on the internet telling that it is wrong:
Currently StateFlow is hot in nature so it’s not recommended to use in repository. For cold and reactive stream, you can use flow, channelFlow or callbackFlow in repository.
Is he right? If he is, how exactly do cold flows help in this situation, and how do we properly manage them?
If it helps, my UI Layer is written solely with Jetpack Compose
In the official "Guide to app architecture" from Google for Android:
About the source of true: ✅ The repository can contain an in-memory-cache.
The source of truth can be a data source—for example, the database—or even an in-memory cache that the repository might contain. Repositories combine different data sources and solve any potential conflicts between the data sources to update the single source of truth regularly or due to a user input event.
About the lifecycle: ✅ You can scope an instance of your repository to the Application class (but take care).
If a class contains in-memory data—for example, a cache—you might want
to reuse the same instance of that class for a specific period of
time. This is also referred to as the lifecycle of the class instance.
If the class's responsibility is crucial for the whole application,
you can scope an instance of that class to the Application class. This
makes it so the instance follows the application's lifecycle.
About the implementation: I recommend you to check the link directly.
class NewsRepository(
private val newsRemoteDataSource: NewsRemoteDataSource
) {
// Mutex to make writes to cached values thread-safe.
private val latestNewsMutex = Mutex()
// Cache of the latest news got from the network.
private var latestNews: List<ArticleHeadline> = emptyList()
suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {
if (refresh || latestNews.isEmpty()) {
val networkResult = newsRemoteDataSource.fetchLatestNews()
// Thread-safe write to latestNews
latestNewsMutex.withLock {
this.latestNews = networkResult
}
}
return latestNewsMutex.withLock { this.latestNews }
}
}
You should read the following page, I think it will answer a lot of your questions : https://developer.android.com/topic/architecture/data-layer
To make this work as a cache you will have to use this repository as a singleton. This effectively create a huge memory leak since you have no control over this memory. You cannot free it, you cannot bypass cache if you want (i mean you can, but it requires additional code outside the flow), you don't have any control over eviction. It's very dumb cache which acts like a memory leak. Not worth it.
Cold flow don't "help" in caching per se. They just give you control over each request that comes from the client. There you can check some outside memory cache if the entry is cached. If yes - is it correct or should be evicted? If it is evicted you can just a normal request. And all this is a single flow that gets disposed right after, so no memory leaks. The only part that have to be singleton is the cache. Although you can implement it as disk cache, it will be faster than network anyway
According to the article below.
https://developer.android.com/jetpack/guide/data-layer#network-request
Make a network request
Making a network request is one of the most common tasks an Android app might perform. The News app needs to present the user with the latest news that is fetched from the network. Therefore, the app needs a data source class to manage network operations: NewsRemoteDataSource. To expose the information to the rest of the app, a new repository that handles operations on news data is created: NewsRepository.
We need a data source class to manage network operations. It's a given example. As you can see API is a parameter of the NewsRemoteDataSource Class.
class NewsRemoteDataSource(
private val newsApi: NewsApi,
private val ioDispatcher: CoroutineDispatcher
) {
/**
* Fetches the latest news from the network and returns the result.
* This executes on an IO-optimized thread pool, the function is main-safe.
*/
suspend fun fetchLatestNews(): List<ArticleHeadline> =
// Move the execution to an IO-optimized thread since the ApiService
// doesn't support coroutines and makes synchronous requests.
withContext(ioDispatcher) {
newsApi.fetchLatestNews()
}
}
}
// Makes news-related network synchronous requests.
interface NewsApi {
fun fetchLatestNews(): List<ArticleHeadline>
}
However, I found lots of repositories like this. They're not using DataSource class. They're implementing API to the repository directly. According to the article above, android suggests the DataSource class to handle network operations. Which example is more effective? Which one should I use? What's the difference between them? Why lots of people are using 2nd one?
class CoinRepositoryImpl #Inject constructor(
private val api: CoinPaprikaApi
) : CoinRepository {
override suspend fun getCoins(): List<CoinDto> {
return api.getCoins()
}
override suspend fun getCoinById(coinId: String): CoinDetailDto {
return api.getCoinById(coinId)
}
}
I'll just mention one of the major building blocks of Development, which is also mentioned in the Object Oriented Programming.
Polymorphism: "The ability to perform a certain action in different ways".
With that said there is also the Principle of open-closed "Objects or entities should be open for extension but closed for modification"
As such, and with that in mind the 1st approach will be better as it allows you to add different DataSources to the Repository class.
And you may also want to add on to it by turning the DataSource to an implementation of an interface. in that way the Repository class might currently be getting the NewsRemoteDataSource, but in a different tab it will receive the LiveScoresRemoteDataSource.
I hope this was helpful in answering your question.
The company I just started working at uses a so called Navigator, which I for now interpreted as a stateless ViewModel. My Navigator receives some usecases, with each contains 1 suspend function. The result of any of those usecases could end up in a single LiveData. The Navigator has no coroutine scope, so I pass the responsibility of scoping suspending to the Fragment using fetchValue().
Most current code in project has LiveData in the data layer, which I tried not to. Because of that, their livedata is linked from view to dao.
My simplified classes:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
val url = MediatorLiveData<String>()
fun goToUrl1() {
url.fetchValue { getUrl1() }
}
fun goToUrl2() {
url.fetchValue { getUrl2() }
}
fun <T> MediatorLiveData<T>.fetchValue(provideValue: suspend () -> T) {
val liveData = liveData { emit(provideValue()) }
addSource(liveData) {
removeSource(liveData)
value = it
}
}
}
class MyFeatureFragment : Fragment {
val viewModel: MyFeatureViewModel by viewModel()
val navigator: MyFeatureNavigator by inject()
fun onViewCreated() {
button.setOnClickListener { navigator.goToUrl1() }
navigator.url.observe(viewLifecycleOwner, Observer { url ->
openUrl(url)
})
}
}
My two questions:
Is fetchValue() a good way to link a suspend function to LiveData? Could it leak? Any other concerns?
My main reason to only use coroutines (and flow) in the data layer, is 'because Google said so'. What's a better reason for this? And: what's the best trade off in being consistent with the project and current good coding practices?
Is fetchValue() a good way to link a suspend function to LiveData?
Could it leak? Any other concerns?
Generally it should work. You probably should remove the previous source of the MediatorLiveData before adding new one, otherwise if you get two calls to fetchValue in a row, the first url can be slower to fetch, so it will come later and win.
I don't see any other correctness concerns, but this code is pretty complicated, creates a couple of intermediate objects and generally difficult to read.
My main reason to only use coroutines (and flow) in the data layer,
is 'because Google said so'. What's a better reason for this?
Google has provided a lot of useful extensions to use coroutines in the UI layer, e.g. take a look at this page. So obviously they encourage people to use it.
Probably you mean the recommendation to use LiveData instead of the Flow in the UI layer. That's not a strict rule and it has one reason: LiveData is a value holder, it keeps its value and provides it immediately to new subscribers without doing any work. That's particularly useful in the UI/ViewModel layer - when a configuration change happens and activity/fragment is recreated, the newly created activity/fragment uses the same view model, subscribes to the same LiveData and receives the value at no cost.
At the same time Flow is 'cold' and if you expose a flow from your view model, each reconfiguration will trigger a new flow collection and the flow will be to execute from scratch.
So e.g. if you fetch data from db or network, LiveData will just provide the last value to new subscriber and Flow will execute the costly db/network operation again.
So as I said there is no strict rule, it depends on the particular use-case. Also I find it very useful to use Flow in view models - it provides a lot of operators and makes the code clean and concise. But than I convert it to a LiveData with help of extensions like asLiveData() and expose this LiveData to the UI. This way I get best from both words - LiveData catches value between reconfigurations and Flow makes the code of view models nice and clean.
Also you can use latest StateFlow and SharedFlow often they also can help to overcome the mentioned Flow issue in the UI layer.
Back to your code, I would implement it like this:
class MyFeatureNavigator(
getUrl1: getUrl1UseCase,
getUrl1: getUrl1UseCase
) {
private val currentUseCase = MutableStateFlow<UseCase?>(null)
val url = currentUseCase.filterNotNull().mapLatest { source -> source.getData()}.asLiveData()
fun goToUrl1() {
currentUseCase.value = getUrl1
}
fun goToUrl2() {
currentUseCase.value = getUrl2
}
}
This way there are no race conditions to care about and code is clean.
And: what's the best trade off in being consistent with the project
and current good coding practices?
That's an arguable question and it should be primarily team decision. In most projects I participated we adopted this rule: when fixing bugs, doing maintenance of existing code, one should follow the same style. When doing big refactoring/implementing new features one should use latest practices adopted by the team.
I am making an app with some friends, and we have decided to go with the MVVM pattern. However, their understanding of the pattern differs from mine.
My question is: If we have a data that we would like to reuse in other views, can we store them as properties in a repository (seeing as the repository pattern is a singleton) and access them from other viewmodels?
Here's a generic example of what I mean:
object AnimalRepository {
val favoriteBreed : Breed? = null
}
and we would access it like so:
class DogViewModel(
application: Application
) : AndroidViewModel(application){
val animalRepository = AnimalRepository
fun setFavoriteBreed(favBreed: Breed) {
animalRepository.favoriteBreed = favBreed
}
fun getFavoriteBreed() : Breed {
return animalRepository.favoriteBreed
}
In this case, i did not make use of LiveData for simplicity purposes.
The debate arose from our different interpretations of this section of Android's guide to app architecture:
https://developer.android.com/jetpack/docs/guide#truth
This is how personally I would use a Repository for and also it is how the repository can be used. Repository is the place where we have data coming from. So that makes it easy for any view or the activity to access the data from directly the Repository and can be used from any ViewModel. Does this answer your question or you need more details?
The bounty expires in 2 days. Answers to this question are eligible for a +100 reputation bounty.
Ethansocal is looking for an answer from a reputable source:
I have this same problem, and would like to know what the best method is.
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.
I would simply inject the Context in the Framework layer!
and the reason for that is quite simple, Context class itself is a part of the Android framework, and your framework layer should autonomously be able to access such classes.
Also, note that you don't even need to have a wrapper class around Context when you inject it in your framework layer. Why? because you aren't going to unit test your framework layer classes. Why? because it would use a mix of Android framework classes or some 3rd party libraries(including other Android libs), that are a black box to us and as a software development principle:
We don't mock dependencies we don't own.