Cover image
Mobile
8 minute read

Better Android Apps Using MVVM With Clean Architecture

Keeping your Android codebase maintainable as your codebase grows can be a challenge. In this article, Toptal Freelance Android Developer Abhishek Tyagi shows how to combine MVVM with Clean Architecture—the latter as described by Robert C. Martin—to write decoupled, testable, and maintainable code.

If you don’t choose the right architecture for your Android project, you will have a hard time maintaining it as your codebase grows and your team expands.

This isn’t just an Android MVVM tutorial. In this article, we are going to combine MVVM (Model-View-ViewModel or sometimes stylized “the ViewModel pattern”) with Clean Architecture. We are going to see how this architecture can be used to write decoupled, testable, and maintainable code.

Why MVVM with Clean Architecture?

MVVM separates your view (i.e. Activitys and Fragments) from your business logic. MVVM is enough for small projects, but when your codebase becomes huge, your ViewModels start bloating. Separating responsibilities becomes hard.

MVVM with Clean Architecture is pretty good in such cases. It goes one step further in separating the responsibilities of your code base. It clearly abstracts the logic of the actions that can be performed in your app.

Note: You can combine Clean Architecture with the model-view-presenter (MVP) architecture as well. But since Android Architecture Components already provides a built-in ViewModel class, we are going with MVVM over MVP—no MVVM framework required!

Advantages of Using Clean Architecture

  • Your code is even more easily testable than with plain MVVM.
  • Your code is further decoupled (the biggest advantage.)
  • The package structure is even easier to navigate.
  • The project is even easier to maintain.
  • Your team can add new features even more quickly.

Disadvantages of Clean Architecture

  • It has a slightly steep learning curve. How all the layers work together may take some time to understand, especially if you are coming from patterns like simple MVVM or MVP.
  • It adds a lot of extra classes, so it’s not ideal for low-complexity projects.

Our data flow will look like this:

The data flow of MVVM with Clean Architecture. Data flows from View to ViewModel to Domain to Data Repository, and then to a Data Source (Local or Remote.)

Our business logic is completely decoupled from our UI. It makes our code very easy to maintain and test.

The example we are going to see is quite simple. It allows users to create new posts and see a list of posts created by them. I’m not using any third-party library (like Dagger, RxJava, etc.) in this example for the sake of simplicity.

The Layers of MVVM with Clean Architecture

The code is divided into three separate layers:

  1. Presentation Layer
  2. Domain Layer
  3. Data Layer

We’ll get into more detail about each layer below. For now, our resulting package structure looks like this:

MVVM with Clean Architecture package structure.

Even within the Android app architecture we’re using, there are many ways to structure your file/folder hierarchy. I like to group project files based on features. I find it neat and concise. You are free to choose whatever project structure suits you.

The Presentation Layer

This includes our Activitys, Fragments, and ViewModels. An Activity should be as dumb as possible. Never put your business logic in Activitys.

An Activity will talk to a ViewModel and a ViewModel will talk to the domain layer to perform actions. A ViewModel never talks to the data layer directly.

Here we are passing a UseCaseHandler and two UseCases to our ViewModel. We’ll get into that in more detail soon, but in this architecture, a UseCase is an action that defines how a ViewModel interacts with the data layer.

Here’s how our Kotlin code looks:

class PostListViewModel(
        val useCaseHandler: UseCaseHandler,
        val getPosts: GetPosts,
        val savePost: SavePost): ViewModel() {


    fun getAllPosts(userId: Int, callback: PostDataSource.LoadPostsCallback) {
        val requestValue = GetPosts.RequestValues(userId)
        useCaseHandler.execute(getPosts, requestValue, object :
        UseCase.UseCaseCallback<GetPosts.ResponseValue> {
            override fun onSuccess(response: GetPosts.ResponseValue) {
                callback.onPostsLoaded(response.posts)
            }

            override fun onError(t: Throwable) {
                callback.onError(t)
            }
        })
    }

    fun savePost(post: Post, callback: PostDataSource.SaveTaskCallback) {
        val requestValues = SavePost.RequestValues(post)
        useCaseHandler.execute(savePost, requestValues, object :
        UseCase.UseCaseCallback<SavePost.ResponseValue> {
            override fun onSuccess(response: SavePost.ResponseValue) {
                callback.onSaveSuccess()
            }
            override fun onError(t: Throwable) {
                callback.onError(t)
            }
        })
    }

}

The Domain Layer

The domain layer contains all the use cases of your application. In this example, we have UseCase, an abstract class. All our UseCases will extend this class.

abstract class UseCase<Q : UseCase.RequestValues, P : UseCase.ResponseValue> {

    var requestValues: Q? = null

    var useCaseCallback: UseCaseCallback<P>? = null

    internal fun run() {
        executeUseCase(requestValues)
    }

    protected abstract fun executeUseCase(requestValues: Q?)

    /**
     * Data passed to a request.
     */
    interface RequestValues

    /**
     * Data received from a request.
     */
    interface ResponseValue

    interface UseCaseCallback<R> {
        fun onSuccess(response: R)
        fun onError(t: Throwable)
    }
}

And UseCaseHandler handles execution of a UseCase. We should never block the UI when we fetch data from the database or our remote server. This is the place where we decide to execute our UseCase on a background thread and receive the response on the main thread.

class UseCaseHandler(private val mUseCaseScheduler: UseCaseScheduler) {

    fun <T : UseCase.RequestValues, R : UseCase.ResponseValue> execute(
            useCase: UseCase<T, R>, values: T, callback: UseCase.UseCaseCallback<R>) {
        useCase.requestValues = values
        useCase.useCaseCallback = UiCallbackWrapper(callback, this)

        mUseCaseScheduler.execute(Runnable {
            useCase.run()
        })
    }

    private fun <V : UseCase.ResponseValue> notifyResponse(response: V,
                                                   useCaseCallback: UseCase.UseCaseCallback<V>) {
        mUseCaseScheduler.notifyResponse(response, useCaseCallback)
    }

    private fun <V : UseCase.ResponseValue> notifyError(
            useCaseCallback: UseCase.UseCaseCallback<V>, t: Throwable) {
        mUseCaseScheduler.onError(useCaseCallback, t)
    }

    private class UiCallbackWrapper<V : UseCase.ResponseValue>(
    private val mCallback: UseCase.UseCaseCallback<V>,
    private val mUseCaseHandler: UseCaseHandler) : UseCase.UseCaseCallback<V> {

        override fun onSuccess(response: V) {
            mUseCaseHandler.notifyResponse(response, mCallback)
        }

        override fun onError(t: Throwable) {
            mUseCaseHandler.notifyError(mCallback, t)
        }
    }

    companion object {

        private var INSTANCE: UseCaseHandler? = null
        fun getInstance(): UseCaseHandler {
            if (INSTANCE == null) {
                INSTANCE = UseCaseHandler(UseCaseThreadPoolScheduler())
            }
            return INSTANCE!!
        }
    }
}

As its name implies, the GetPosts UseCase is responsible for getting all posts of a user.


class GetPosts(private val mDataSource: PostDataSource) :
UseCase<GetPosts.RequestValues, GetPosts.ResponseValue>() {

    protected override fun executeUseCase(requestValues: GetPosts.RequestValues?) {
        mDataSource.getPosts(requestValues?.userId ?: -1, object :
        PostDataSource.LoadPostsCallback {
            override fun onPostsLoaded(posts: List<Post>) {
                val responseValue = ResponseValue(posts)
                useCaseCallback?.onSuccess(responseValue)
            }
            override fun onError(t: Throwable) {
                // Never use generic exceptions. Create proper exceptions. Since
                // our use case is different we will go with generic throwable
                useCaseCallback?.onError(Throwable("Data not found"))
            }
        })
    }
    class RequestValues(val userId: Int) : UseCase.RequestValues
    class ResponseValue(val posts: List<Post>) : UseCase.ResponseValue
}

The purpose of the UseCases is to be a mediator between your ViewModels and Repositorys.

Let’s say in the future you decide to add an “edit post” feature. All you have to do is add a new EditPost UseCase and all its code will be completely separate and decoupled from other UseCases. We’ve all seen it many times: New features are introduced and they inadvertently break something in preexisting code. Creating a separate UseCase helps immensely in avoiding that.

Of course, you can’t eliminate that possibility 100 percent, but you sure can minimize it. This is what separates Clean Architecture from other patterns: The code is so decoupled that you can treat every layer as a black box.

The Data Layer

This has all the repositories which the domain layer can use. This layer exposes a data source API to outside classes:

interface PostDataSource {

    interface LoadPostsCallback {
        fun onPostsLoaded(posts: List<Post>)
        fun onError(t: Throwable)
    }

    interface SaveTaskCallback {
        fun onSaveSuccess()
        fun onError(t: Throwable)
    }

    fun getPosts(userId: Int, callback: LoadPostsCallback)
    fun savePost(post: Post)
}

PostDataRepository implements PostDataSource. It decides whether we fetch data from a local database or a remote server.

class PostDataRepository private constructor(
        private val localDataSource: PostDataSource,
        private val remoteDataSource: PostDataSource): PostDataSource {

    companion object {
        private var INSTANCE: PostDataRepository? = null
        fun getInstance(localDataSource: PostDataSource,
        remoteDataSource: PostDataSource): PostDataRepository {
            if (INSTANCE == null) {
                INSTANCE = PostDataRepository(localDataSource, remoteDataSource)
            }
            return INSTANCE!!
        }
    }
    var isCacheDirty = false
    override fun getPosts(userId: Int, callback: PostDataSource.LoadPostsCallback) {
        if (isCacheDirty) {
            getPostsFromServer(userId, callback)
        } else {
            localDataSource.getPosts(userId, object : PostDataSource.LoadPostsCallback {
                override fun onPostsLoaded(posts: List<Post>) {
                    refreshCache()
                    callback.onPostsLoaded(posts)
                }
                override fun onError(t: Throwable) {
                    getPostsFromServer(userId, callback)
                }
            })
        }
    }
    override fun savePost(post: Post) {
        localDataSource.savePost(post)
        remoteDataSource.savePost(post)
    }
    private fun getPostsFromServer(userId: Int, callback: PostDataSource.LoadPostsCallback) {
        remoteDataSource.getPosts(userId, object : PostDataSource.LoadPostsCallback {
            override fun onPostsLoaded(posts: List<Post>) {
                refreshCache()
                refreshLocalDataSource(posts)
                callback.onPostsLoaded(posts)
            }
            override fun onError(t: Throwable) {
                callback.onError(t)
            }
        })
    }
    private fun refreshLocalDataSource(posts: List<Post>) {
        posts.forEach {
            localDataSource.savePost(it)
        }
    }
    private fun refreshCache() {
        isCacheDirty = false
    }
}

The code is mostly self-explanatory. This class has two variables, localDataSource and remoteDataSource. Their type is PostDataSource, so we don’t care how they are actually implemented under the hood.

In my personal experience, this architecture has proved to be invaluable. In one of my apps, I started with Firebase on the back end which is great for quickly building your app. I knew eventually I’d have to shift to my own server.

When I did, all I had to do was change the implementation in RemoteDataSource. I didn’t have to touch any other class even after such a huge change. That is the advantage of decoupled code. Changing any given class shouldn’t affect other parts of your code.

Some of the extra classes we have are:

interface UseCaseScheduler {

    fun execute(runnable: Runnable)

    fun <V : UseCase.ResponseValue> notifyResponse(response: V,
                                                   useCaseCallback: UseCase.UseCaseCallback<V>)

    fun <V : UseCase.ResponseValue> onError(
            useCaseCallback: UseCase.UseCaseCallback<V>, t: Throwable)
}


class UseCaseThreadPoolScheduler : UseCaseScheduler {

    val POOL_SIZE = 2

    val MAX_POOL_SIZE = 4

    val TIMEOUT = 30

    private val mHandler = Handler()

    internal var mThreadPoolExecutor: ThreadPoolExecutor

    init {
        mThreadPoolExecutor = ThreadPoolExecutor(POOL_SIZE, MAX_POOL_SIZE, TIMEOUT.toLong(),
                TimeUnit.SECONDS, ArrayBlockingQueue(POOL_SIZE))
    }

    override fun execute(runnable: Runnable) {
        mThreadPoolExecutor.execute(runnable)
    }

    override fun <V : UseCase.ResponseValue> notifyResponse(response: V,
                                                   useCaseCallback: UseCase.UseCaseCallback<V>) {
        mHandler.post { useCaseCallback.onSuccess(response) }
    }

    override fun <V : UseCase.ResponseValue> onError(
            useCaseCallback: UseCase.UseCaseCallback<V>, t: Throwable) {
        mHandler.post { useCaseCallback.onError(t) }
    }

}

UseCaseThreadPoolScheduler is responsible for executing tasks asynchronously using ThreadPoolExecuter.


class ViewModelFactory : ViewModelProvider.Factory {


    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass == PostListViewModel::class.java) {
            return PostListViewModel(
                    Injection.provideUseCaseHandler()
                    , Injection.provideGetPosts(), Injection.provideSavePost()) as T
        }
        throw IllegalArgumentException("unknown model class $modelClass")
    }

    companion object {
        private var INSTANCE: ViewModelFactory? = null
        fun getInstance(): ViewModelFactory {
            if (INSTANCE == null) {
                INSTANCE = ViewModelFactory()
            }
            return INSTANCE!!
        }
    }
}

This is our ViewModelFactory. You have to create this to pass arguments in your ViewModel constructor.

Dependency Injection

I’ll explain dependency injection with an example. If you look at our PostDataRepository class, it has two dependencies, LocalDataSource and RemoteDataSource. We use the Injection class to provide these dependencies to the PostDataRepository class.

Injecting dependency has two main advantages. One is that you get to control the instantiation of objects from a central place instead of spreading it across the whole codebase. Another is that this will help us write unit tests for PostDataRepository because now we can just pass mocked versions of LocalDataSource and RemoteDataSource to the PostDataRepository constructor instead of actual values.

object Injection {

    fun providePostDataRepository(): PostDataRepository {
        return PostDataRepository.getInstance(provideLocalDataSource(), provideRemoteDataSource())
    }

    fun provideViewModelFactory() = ViewModelFactory.getInstance()

    fun provideLocalDataSource(): PostDataSource = LocalDataSource.getInstance()

    fun provideRemoteDataSource(): PostDataSource = RemoteDataSource.getInstance()

    fun provideGetPosts() = GetPosts(providePostDataRepository())

    fun provideSavePost() = SavePost(providePostDataRepository())

    fun provideUseCaseHandler() = UseCaseHandler.getInstance()
}

Note: I prefer using Dagger 2 for dependency injection in complex projects. But with its extremely steep learning curve, it’s beyond the scope of this article. So if you’re interested in going deeper, I highly recommend Hari Vignesh Jayapalan’s introduction to Dagger 2.

MVVM with Clean Architecture: A Solid Combination

Our purpose with this project was to understand MVVM with Clean Architecture, so we skipped over a few things that you can try to improve it further:

  1. Use LiveData or RxJava to remove callbacks and make it a little neater.
  2. Use states to represent your UI. (For that, check out this amazing talk by Jake Wharton.)
  3. Use Dagger 2 to inject dependencies.

This is one of the best and most scalable architectures for Android apps. I hope you enjoyed this article, and I look forward to hearing how you’ve used this approach in your own apps!


Further Reading on the Toptal Engineering Blog:

Understanding the basics

Android architecture is the way you structure your Android project code so that your code is scalable and easy to maintain. Developers spend more time maintaining a project than initially building it, so it makes sense to follow a proper architectural pattern.

In Android, MVC refers to the default pattern where an Activity acts as a controller and XML files are views. MVVM treats both Activity classes and XML files as views, and ViewModel classes are where you write your business logic. It completely separates an app's UI from its logic.

In MVP, the presenter knows about the view and view knows about the presenter. They interact with each other through an interface. In MVVM, only the view knows about the view-model. The view-model has no idea about the view.

One is separation of concerns, i.e. your business logic, UI, and data models should live at different places. Another is the decoupling of code: Every piece of code should act as a black box so that changing anything in a class should not have any effect on another part of your codebase.

Robert C. Martin's "Clean Architecture" is a pattern that lets you break down your interaction with data into simpler entities called "use cases." It is great for writing decoupled code.

Most apps save and retrieve data, either from local storage or a remote server. Android repositories are classes that decide whether data should come from a server or local storage, decoupling your storage logic from outside classes.

Comments

Nithy Anandh
nice
Nithy Anandh
nice
Janine Gardiola
oohh interesting
Janine Gardiola
oohh interesting
Reza
Thank you for great explanation. But i still have some problem to implement this in multi accounts apps. Not sure where we should put account/authorization related information when accessing remote repository. Let's extend your example above where i could get posts from different news provider. In this case we will have several accounts with several access tokens for each news provider. Say that when the first app run we only fetch posts from current active account. And from the same screen user can do switch account to different account then fetch posts from another news provider. Also there could be some operation which run in parallel for all accounts, for example we need to do polling to each news provider to get certain information. Activity -> ViewModel -> UseCase -> Repository -> Remote News Provider Where do you put the access token or utilize account data in above situation? Thank you.
Reza
Thank you for great explanation. But i still have some problem to implement this in multi accounts apps. Not sure where we should put account/authorization related information when accessing remote repository. Let's extend your example above where i could get posts from different news provider. In this case we will have several accounts with several access tokens for each news provider. Say that when the first app run we only fetch posts from current active account. And from the same screen user can do switch account to different account then fetch posts from another news provider. Also there could be some operation which run in parallel for all accounts, for example we need to do polling to each news provider to get certain information. Activity -> ViewModel -> UseCase -> Repository -> Remote News Provider Where do you put the access token or utilize account data in above situation? Thank you.
Abhishek Tyagi
There is no one solution for all. Everything depends on your use case. For the case you mentioned, I would implement a <code>GetPosts</code> Usecase which takes userId as the <code>RequestValue</code>. This Usecase request the <code>Repository</code> class for posts, now it is upto repository how it want to fetch that data. First query your local storage for access-token based on userId, then make a remote call to <code>RemoteNewProvider</code> <blockquote>Where do you put the access token or utilize account data in above situation?</blockquote> Your access token should be in your local storage as you will need it in every api call (or you can cache it in memory depending on your case). Your <code>UseCase</code> classes should never know anything about user's account data. You should send some identifier (ex - userId) from your viewmodel which would be used to know which account is asking for posts. Does that answer your question?
Abhishek Tyagi
There is no one solution for all. Everything depends on your use case. For the case you mentioned, I would implement a <code>GetPosts</code> Usecase which takes userId as the <code>RequestValue</code>. This Usecase request the <code>Repository</code> class for posts, now it is upto repository how it want to fetch that data. First query your local storage for access-token based on userId, then make a remote call to <code>RemoteNewProvider</code> <blockquote>Where do you put the access token or utilize account data in above situation?</blockquote> Your access token should be in your local storage as you will need it in every api call (or you can cache it in memory depending on your case). Your <code>UseCase</code> classes should never know anything about user's account data. You should send some identifier (ex - userId) from your viewmodel which would be used to know which account is asking for posts. Does that answer your question?
Reza
I see, so in this case only Repository layer knows how to fetch the token for each account from account manager instance through it's interface. For example wrapping android AccountManager then pass it as constructor parameter for Repository.
Reza
I see, so in this case only Repository layer knows how to fetch the token for each account from account manager instance through it's interface. For example wrapping android AccountManager then pass it as constructor parameter for Repository.
Abhishek Tyagi
It's hard to say without understanding how you are using Account Manager but that is one of the way.
Abhishek Tyagi
It's hard to say without understanding how you are using Account Manager but that is one of the way.
Reza
No problem. I understand better now.
Reza
No problem. I understand better now.
Alexandre Navarro
Can you share a repository on github, for example, with this example?
Alexandre Navarro
Can you share a repository on github, for example, with this example?
Alex Bin Zhao
The UseCaseHandler part is weird design, why would you execute your use case on background design. It is should be responsibility of the repository to fetch data in the background, user case shouldn't be aware of these details.
Alex Bin Zhao
The UseCaseHandler part is weird design, why would you execute your use case on background design. It is should be responsibility of the repository to fetch data in the background, user case shouldn't be aware of these details.
Abhishek Tyagi
Anything that happens in <code>Repository</code> class should be on background thread. I can't think of a single use case where <code>Repository</code> class needs to do anything on main thread. It should never know anything about threads. It should execute call on whatever thread it is called on. Another reason I can think is switching thread in <code>Repository</code> class will make it a nightmare to write tests for it.
Abhishek Tyagi
Anything that happens in <code>Repository</code> class should be on background thread. I can't think of a single use case where <code>Repository</code> class needs to do anything on main thread. It should never know anything about threads. It should execute call on whatever thread it is called on. Another reason I can think is switching thread in <code>Repository</code> class will make it a nightmare to write tests for it.
Vinayak
If user wants to perform data fetch from server and let the app user to keep waiting.. Then how will activity knows that its fetching data from from server and show please wait message.
Vinayak
If user wants to perform data fetch from server and let the app user to keep waiting.. Then how will activity knows that its fetching data from from server and show please wait message.
Abhishek Tyagi
All the requests begin from activity. When you call Viewmodel's method to execute a particular UseCase, that's when you show the loading message and remove it when you get response from the Viewmodel.
Abhishek Tyagi
All the requests begin from activity. When you call Viewmodel's method to execute a particular UseCase, that's when you show the loading message and remove it when you get response from the Viewmodel.
Vinayak
Hi Abhishek, I am little confused with MvVM architecture. Can you please share sample project code? It should include viewmodel, repository, retrofit api call, showing progress while data fetching. I had seen many GitHub projects but did not get right file structure for it. I will appreciate if you could give guide me through this.
Vinayak
Hi Abhishek, I am little confused with MvVM architecture. Can you please share sample project code? It should include viewmodel, repository, retrofit api call, showing progress while data fetching. I had seen many GitHub projects but did not get right file structure for it. I will appreciate if you could give guide me through this.
Shoaib Ahmad
Simply elegant. I'm just amazed by the power of Kotlin and the way it beautifies clean architecture.
Maksim Novikov
Hi, i see you provide callback into ViewModel class from UI classes, as i know this break the whole purpose of the viewmodel, as recommended by Google the ViewModel should never have instance of the view. The question is, lets say i have Entity User and it can be fetched from local and remote source, i will create interface UserDataSource which will have the CRUD methods, the problem is what if i want to user Room as local storage and i want to use it with LiveData this means my UserLocalDataSource need to return LiveData and my UserRemoteDataSource should use regular callback, so how can i use same DataSource interface in this situation?
Abhishek Tyagi
Hi Maksim, We are not storing the view instance in ViewModel. The callback is passed to a method and that callback is not a View or Activity. For your second question, you can make the remote call in Repository class and return the LiveData query object from Room. Once you receive response from the server, just only update you local database and it will trigger Livedata to update your view. Something like this: <code>override fun getPosts(userId: Int): LiveData<List<Post>> { getPostsFromServer(userId, callback, object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded(posts: List<Post>) { localDataSource.updatePosts(posts); } }) return localDataSource.getPosts(userId); }</code> The call structure will depend on your use case but the gist is similar.
Maksim Novikov
But this means i would have to create 2 different UserDataSource interfaces, one for local and one for remote because the remote will be like this: remoteSource.getUsers(callback: Callback<List<User>>) and the localSource will be like this: localSource.getUsers(): LivedData<List<User>> and inside the repository i will have to manually create LiveData, the question is there a way to avoid creating double UserDataSource interfaces?
Abhishek Tyagi
Yes you will have to create two UserData Source interfaces and ideally you actually should. You don't have to create LiveData manually in Repository. Room has support for LiveData and it can return LiveData for queries.
Maksim Novikov
It is not a problem for me to create <code>interface RemoteUserDataSource</code> and <code>interface LocalUserDataSource </code>and then their implementation, i just saw that you use same interface for both: <code>class PostDataRepository private constructor( private val localDataSource: PostDataSource, private val remoteDataSource: PostDataSource)</code> and thought it is for reason.
Abhishek Tyagi
For simple use cases single interface is enough but it is not the right choice for complex use cases. You can still keep the same interface if you want by returning LiveData from the Remote Data Source as well. If you use Rx then it's much easier to keep them similar. I prefer to keep them separate to avoid limitations. All architectures are mostly a guidance rather than a rule. You can customise any part based on your needs. Nothing is a rule.
Maksim Novikov
Ok thanks, of many articles i have read yours is the most simple to understand and practical for real world. Last question :) i'm also working on the server side for the app i'm using a NodeJs, Express and Mongoose as main frameworks for the server, after creating the basic crud operations i see that the code grows very quick and soon will become mess, do you know some good articles about architecture for those frameworks, something similar to current one?
Abhishek Tyagi
I'm sorry but I don't
niggaWHAT
It's same as model class as you have in MVP, model fetch responses which runs in background. Also your model has a reference of Central Repository. its aliases are Interactor, Use-Case, Model whatever. Just fetch/set either from bg or main and process the result that before sending it to presenter.
Hemeanandh Anandh
@ab@disqus_08Otq9HZXx:disqus Can you share the github link with the complete source code?
Kucing Kiki
no source code or github link ?
Ricard
You are violating the Dependency Inversion Principle of SOLID, you're using the implementation itself instead of the interface to communicate between layers, for example PostListViewModel knows all the GetPosts implementation, same for GetPosts and PostDataRepository.
Abhishek Tyagi
GetPosts is a UseCase. It will always have only one function. Cost of abstracting it is too high. You can have thousands of UseCases in a project and maintaining an interface for each <code>UseCase</code> is too huge of a cost than the benefit. You are right about the second part. GetPosts shouldn't know about PostDataRepository. It should only have access to <code>PostDataSource</code>
mobibob
The project structure discussion started to get vague and did not match the other narrative. I would love to see the mapping back of the package to the arch diagram. Also, when I tried to use an Android built-in Activity that has a model, it did not align structurally, although it had all the representative files (classes, models, repository, etc.). Therefore, I continue to struggle following the project pattern in my new projects.
Vinod Gangwar
https://github.com/vinod1207/KotlinBaseApp
M. Philips
Approved
phonebanshee
I'm with Maksim here - the callbacks are a problem. "We are not storing the view instance in ViewModel." You really can't have callback objects passed to ViewModels, since it's way to easy to have the callbacks capture a reference to a UI element. You really do want to pull in something like rx, or livedata, since building a system to be able to clear callbacks isn't a great idea either. And personally, you need to find every instance of "var" in your codebase, pour petrol on it, and light it on fire. You should be using GOTO more often than "var". Immutable data, always and everywhere, unless you are absolutely certain you have to have a var. And you probably don't.
ManU4Life
Wouldn't coroutines be a better alternative to using a scheduler.
Mark Buikema
If I have some static text in my UI, for example a toolbar title, should I: 1. Just set the text in the fragment, or 2. Let the ViewModel post the title to a LiveData and set the toolbar title in the observer?
Jobrane Amami
The code is mostly self-explanatory...
Maxeem
<b>Thank you, Abhishek! Great post!</b>
IronHide
if this is mvvm + CA what happened to the Model from mvvm ?
comments powered by Disqus