Skip to main content

MVVM

The Layers of MVVM

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:

  • Models
Models
  • Modules
Models
  • Networks
Models

The Presentation Layer

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

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’s how code looks:

  • Activity Class

    class LoginActivity : AppCompatActivity() {

    //Data Binding
    private lateinit var binding: ActivityLoginBinding

    //View Model
    private lateinit var viewModel: LoginActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityLoginBinding.inflate(layoutInflater)
    setContentView(binding.root)

    initVar()
    initObserver()
    }

    private fun initVar() {
    viewModel = ViewModelProvider(this)[LoginActivityViewModel::class.java]
    progressDialog = Helpers.getProgressDialog(this)!!
    }

    private fun initObserver() {
    viewModel.isLoading.observe(this) { isLoading ->
    /// TODO Do your action here
    }

    viewModel.errorMessage.observe(this) { message ->
    /// TODO Do your action here
    }

    viewModel.loginResponseMutableLiveData.observe(this) { response ->
    /// TODO Do your action here
    }
    }
    }
  • ViewModel Class

    class LoginActivityViewModel(application: Application) : AndroidViewModel(application) {

    //Repository
    private var loginRepository: LoginRepository = LoginRepository()

    //Object
    var loginResponseMutableLiveData: MutableLiveData<ProfileResponse> = MutableLiveData()

    //ETC
    var isLoading: MutableLiveData<Boolean> = MutableLiveData()
    var errorMessage: MutableLiveData<String> = MutableLiveData()

    fun postLogin(email: String, password: String) {

    when {
    email.isEmpty() -> {
    errorMessage.value = "Email cannot be empty"
    }
    password.isEmpty() -> {
    errorMessage.value = "Passwowrd cannot be empty"
    }
    else -> {
    val body = LoginRequest(
    email = email,
    password = password,
    )

    isLoading.value = true

    loginRepository.postLogin(
    body,
    SharedPreferenceServices.getToken(getApplication()),
    object : ApiResponseCallback<ProfileResponse?> {
    override fun onSuccess(response: ProfileResponse?) {
    isLoading.value = false
    loginResponseMutableLiveData.value = response
    errorMessage.value = null
    }

    override fun onFailure(error: String?) {
    isLoading.value = false
    loginResponseMutableLiveData.value = null
    errorMessage.value = error
    }
    })
    }
    }
    }

    }

The Domain Layer

The domain layer contains all the use cases of your application.

In this example, we have:

  1. RetrofitService, an class for HTTP Request.

    class RetrofitService {
    companion object {
    private val NETWORK_TIMEOUT = 600

    fun getRetrofitInstance(context: Context?): Retrofit? {
    val builder = GsonBuilder()

    val gson = builder
    .setLenient()
    .create()

    val retrofit: Retrofit.Builder = Retrofit.Builder()
    .baseUrl(BuildConfig.BASE_URL_APP)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())

    val httpClient: OkHttpClient.Builder =
    OkHttpClient.Builder()
    .addInterceptor(Interceptor { chain: Interceptor.Chain ->
    val original = chain.request()
    val request =
    original.newBuilder()
    .method(original.method, original.body)
    .build()
    chain.proceed(request)
    })
    .connectTimeout(NETWORK_TIMEOUT.toLong(), TimeUnit.SECONDS)
    .readTimeout(NETWORK_TIMEOUT.toLong(), TimeUnit.SECONDS)
    .writeTimeout(NETWORK_TIMEOUT.toLong(), TimeUnit.SECONDS)

    //Add Logging Interceptor
    if (BuildConfig.DEBUG) {
    val logging = HttpLoggingInterceptor()
    logging.setLevel(HttpLoggingInterceptor.Level.BODY)
    httpClient.addInterceptor(logging)
    }

    retrofit.client(httpClient.build())
    return retrofit.build()
    }
    }
    }
  2. ApiResponseCallback, an interface class for callback.

    interface ApiResponseCallback<in TData> {

    fun onSuccess(response: TData?)
    fun onFailure(error: String?)

    }

    The purpose of the ApiResponseCallback is to be a mediator for response from API between your ViewModels and Repositories.

  3. ApiInterface, an interface class for call the API endpoints.

    interface ApiInterface {

    @POST(value = "/authorization/handheld/login")
    fun postLogin(
    @Body body: LoginRequest
    ): Observable<BaseResponse<ProfileResponse>>

    }

    The purpose of the ApiInterface is to be a mediator for call API between your ViewModels and Repositories.

  4. Singleton, an class for communicate between Repository and HTTP Request

    class Singleton {
    companion object {

    @SuppressLint("StaticFieldLeak")
    private lateinit var context: Context

    fun setContext(con: Context) {
    context = con
    }

    private fun getApplicationContext(): Context {
    return context
    }

    fun getService(): ApiInterface? {
    return RetrofitService.getRetrofitInstance(getApplicationContext())
    ?.create(ApiInterface::class.java)
    }
    }
    }

The Data Layer

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

class LoginRepository {
private val compositeDisposable = CompositeDisposable()

fun postLogin(
body: LoginRequest,
token: String,
callback: ApiResponseCallback<ProfileResponse?>
) {
Singleton.getService(token)?.postLogin(body)
?.observeOn(AndroidSchedulers.mainThread())
?.subscribeOn(Schedulers.io())?.let {
compositeDisposable.add(
it
.subscribe({ response -> callback.onSuccess(response.data) }) { error ->
callback.onFailure(
Helpers.onFailed(error)
)
})
}
}
}

Source Toptal - Abhishek Tyagi