Kotlin with MVVM application PART 1

Süleyman Başaranoğlu
6 min readNov 22, 2021

--

Hi everyone, in this article, as in the title, I will try to develop an application using the MVVM design pattern and share it as open-source on Github. This is going to be a bit long, so I’ll break the article down into sections. Let’s start by explaining which technologies and libraries I will use in the application.

MVVM design pattern; Jetpack Navigation, Retrofit, RxJava, Coroutine, Firebase, Firestore, View Binding, Koin, Picasso, Shared Preferences, LiveData,

First, our application must have an algorithm. This algorithm will make our work easier by presenting us with a road map.

1.1 Algorithm Flowchart

We will try to progress the information section on the Flowchart. After creating our Gradle file and editing it according to this section, start by performing our navigation rules.

def nav_version = "2.3.5"
def koin_version= "3.1.2"
//navigation jetpack
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
//firebase
implementation platform('com.google.firebase:firebase-bom:28.3.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-firestore-ktx'
implementation 'com.google.firebase:firebase-storage-ktx'
implementation 'com.google.firebase:firebase-auth:21.0.1'
implementation 'com.google.firebase:firebase-database:20.0.2'
//material design
implementation 'com.google.android.material:material:1.4.0'
implementation "io.insert-koin:koin-core:$koin_version"
// Koin main features for Android (Scope,ViewModel ...)
implementation "io.insert-koin:koin-android:$koin_version"
// Koin Java Compatibility
implementation "io.insert-koin:koin-android-compat:$koin_version"
// Koin for Jetpack WorkManager
implementation "io.insert-koin:koin-androidx-workmanager:$koin_version"

Let’s create a firebase project by clicking this article. After finishing the article and adding it to our project (if you can’t, please contact me), let’s create a navigation resource file for navigation operations as follows and name it my_navigation.

2.1 Navigation Resource File

Now add 3 fragments that we will create in the first step; Login, Register, and Information.

2.2 Add Fragments

When I connect Fragments with arrows as in Picture 2.3, it creates files like LoginFragmentDirection in the java-generated folder and outputs actions.

<fragment // Just for login fragment
android:id="@+id/loginFragment"
android:name="com.example.mustfitmvvmjetpack.ui.LoginFragment"
android:label="fragment_login"
tools:layout="@layout/fragment_login" >
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment" />
<action
android:id="@+id/action_loginFragment_to_informationFragment"
app:destination="@id/informationFragment" />
</fragment>
2.3 Actions

After doing the above operations, we need to write the navigation process in the main activity.

<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/my_navigation" />

Create a file named UI and a fragment named Login in the Layout section. After adding 2 edit texts and 2 buttons, let’s make etLoginEmail(id) for email, etLoginPassword(id) for the password, loginButton(id) for a login button, registerButton(id) for I don’t have an account button.

Now create a file named repo and define a class named AuthenticationRepository in it. Here we will do the procedures related to authentication.

In this step create a function called login in the repository and request the email and password parameters as strings. First, we need to define 3 lateinit variables to listen to firebase and livedata.

private lateinit var _firebaseAuth: FirebaseAuth
private lateinit var _userLiveData: MutableLiveData<FirebaseUser>
private lateinit var _messageLiveData: MutableLiveData<String>
fun login(email: String, password: String) {_firebaseAuth = FirebaseAuth.getInstance()
_userMutableLiveData = MutableLiveData()
_liveDataMessage = MutableLiveData<String>()

We created the above function and called the variables. We will check the email and password below.

If the control is empty we will toast with liveDataMessage. If the control is not empty let’s listen with userMutableLiveData and write the successful or unsuccessful login status code according to the email and password information.

if (email == "" || password == "") {
_messageLiveData.postValue("Email and password must not be empty")
} else {
_firebaseAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
_userLiveData.postValue(firebaseAuth.currentUser)
} else {
_liveDataMessage.postValue("Register Failed")
}
}
}

note: Maybe use isNotEmpty() for control ==” “

Now we create a file named viewmodel and define a class called AuthenticationViewModel in it.

We will derive our Viewmodel from AndroidViewModel and AndroidViewModel expects application variable due to its structure below, we need to return it inside.

public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}

Let us define the application variable in our class and pass it into AndroidViewModel as follows.

class AuthenticationViewModel(application: Application) : AndroidViewModel(application)

We will need 2 lateinit variables, they are necessary to be able to call our repository and to listen to live data, respectively.

private lateinit var _appRepository: AuthenticationRepository
private lateinit var _userMutableLiveData: MutableLiveData<FirebaseUser>

We accessed the appRepository, which we defined as lateinit below, and called the login function in it. We synchronized the email we defined here to the email variable we want to be in the repo and the password we defined here to the password variable in the repo as in the code below.

fun login(email: String, password: String) {
_appRepository = AuthenticationRepository()
_appRepository.login(email = email, password = password)
}

Now we create Koin Module for Dependency Injection. Let’s create a file named di and create an object file named KoinModule. Now we need a variable for Koinmodule. Let’s write this code.

object KoinModule {

val viewModelModule = module {
viewModel { AuthenticationViewModel(get()) }
}
}

We need to make some changes in ViewModel like this ;

class AuthenticationViewModel(application: Application) : AndroidViewModel(application),
KoinComponent <-

Let us create an Application class for Koin and create function instertKoin() ;

class MyApplication : Application() {

override fun onCreate() {
super.onCreate()
insertKoin()
}

private fun insertKoin(){
startKoin {
androidContext(this@MyApplication)
modules(listOf(KoinModule.viewModelModule))
}
}
}

Now everything is good for Koin in the View part, let’s define a function by entering the login fragment and letting the function name be loginClicked in order to clearly describe what it does.

private fun loginClicked()

For calling View Model ;

private lateinit var _viewModel: AuthenticationViewModel  

Define in OnCreate ;

_viewModel = ViewModelProvider(this).get(AuthenticationViewModel::class.java)

Use View Binding ;

private lateinit var _binding: FragmentLoginBinding

In build.gradle

android { 
.
.
.
buildFeatures {
viewBinding = true
}

In onCreateView

_binding = FragmentLoginBinding.inflate(inflater, container, false) 
.
.
.
return _binding.root

Now fill the clicked function with the data we will reach with viewmodel and binding.

_binding.loginButton.setOnClickListener {_viewModel.login(
email = _binding.etLoginEmail.text.toString(),
password = _binding.etLoginPassword.text.toString()
)

After using Jetpack Navigation

Navigation.findNavController(it)
.navigate(LoginFragmentDirections.actionLoginFragmentToInformationFragment())

Do not have an account button ;

private fun registerClicked() {
_binding.registerButton.setOnClickListener {
Navigation.findNavController(it)
.navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment())
}

Get loginClickedOnLoginPage(), registerClickedOnLoginPage() in onCreateView

If I summarize what I’ve done so far ;

1-For navigation ; create my_navgiation and add actions.

2-Create ui folder and add Login Fragment, Register Fragment, Information Fragment.

3-Get id’s for ui items

4-Create Repository here we will do the procedures related to authentication.

5-Create ViewModel and we got code-functions in repo

6-In UI With the function we created on the view model, we assigned data to the variables with view binding.

We have to do the same things for Register, I’m just sharing the code below

AuthenticationRepository

fun register(email: String, password: String) {
firebaseAuth = FirebaseAuth.getInstance()
application = Application()
userMutableLiveData = MutableLiveData()
liveDataMessage = MutableLiveData<String>()

if (email == "" || password == "") {
liveDataMessage.postValue("Email and password must not be empty")
} else {
firebaseAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
userMutableLiveData.postValue(firebaseAuth.currentUser)
liveDataMessage.postValue("Register Success")
} else {
liveDataMessage.postValue("Register Failed")
}
}
}
}

AuthenticationViewModel

fun register(email: String, password: String) {
appRepository = AuthenticationRepository()
appRepository.register(email = email, password = password)
}

UI REGİSTER

private fun registerClickedOnRegisterPage() {
binding.signupButton.setOnClickListener {
viewModel.register(
email = binding.etRegisterEmail.text.toString(),
password = binding.etRegisterPassword.text.toString()
)
}
}
private fun loginClickedOnRegisterPage() {
binding.tvGoLogin.setOnClickListener {
Navigation.findNavController(it)
.navigate(RegisterFragmentDirections.actionRegisterFragmentToLoginFragment())
}
}

In this article, we have completed the part up to Information on our flowchart. In the next article, I will complete between Information and Profile.

--

--