Login API with retrofit and MVVM with auto-login in android kotlin.

Harshita Bambure
5 min readJun 24, 2022

--

Today we are going to learn how to do login using android kotlin with retrofit MVVM.

For API implementation I am using this website.

First, we need to add internet permission to the manifest file.

<uses-permission android:name="android.permission.INTERNET" />

Now we need to add dependency in the build.Gradle(:app)

//RetroFit Dependencies
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.1'

//Coroutains"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' //viewModel scope
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1' //lifecycle scope
implementation 'androidx.fragment:fragment-ktx:1.4.1'

//Lifecycle
implementation 'androidx.lifecycle:lifecycle-common:2.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
//size dp/sp
implementation 'com.intuit.sdp:sdp-android:1.0.6'
implementation 'com.intuit.ssp:ssp-android:1.0.6'

implementation "androidx.preference:preference-ktx:1.2.0"

after that, we create one object file for adding the base URL.

Constant.kt

object Constant {
const val BASE_URL = "http://restapi.adequateshop.com"
}

here I am using HTTP instead of HTTPS that's why I am adding a network security config file in XML/network_security_config.

network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">http://restapi.adequateshop.com</domain>
</domain-config>
</network-security-config>

Now we need to create API requests and response classes.

LoginRequest.kt

import com.google.gson.annotations.SerializedName

data class LoginRequest(
@SerializedName("email")
var email: String,
@SerializedName("password")
var password: String
)

LoginResponse.kt

import com.google.gson.annotations.SerializedName

data class LoginResponse(
@SerializedName("code")
var code: Int,
@SerializedName("data")
var `data`: Data,
@SerializedName("id")
var id: String,
@SerializedName("message")
var message: String
) {
data class Data(
@SerializedName("Email")
var email: String,
@SerializedName("id")
var id: String,
@SerializedName("Id")
var id2: Int,
@SerializedName("Name")
var name: String,
@SerializedName("Token")
var token: String
)
}

BaseResponse.kt

sealed class BaseResponse<out T> {
data class Success<out T>(val data: T? = null) : BaseResponse<T>()
data class Loading(val nothing: Nothing?=null) : BaseResponse<Nothing>()
data class Error(val msg: String?) : BaseResponse<Nothing>()
}

Here we add our retrofit client class.

ApiClient.kt

object ApiClient {
var mHttpLoggingInterceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)

var mOkHttpClient = OkHttpClient
.Builder()
.addInterceptor(mHttpLoggingInterceptor)
.build()

var mRetrofit: Retrofit? = null


val client: Retrofit?
get() {
if(mRetrofit == null){
mRetrofit = Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.client(mOkHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return mRetrofit
}
}

After adding the retrofit client class now we need to add an API interface.

UserApi.kt

interface UserApi {

@POST("/api/authaccount/login")
suspend fun loginUser(@Body loginRequest: LoginRequest): Response<LoginResponse>

companion object {
fun getApi(): UserApi? {
return ApiClient.client?.create(UserApi::class.java)
}
}
}

After creating the API interface now we need to add a repository class.

UserRepository.kt

class UserRepository {

suspend fun loginUser(loginRequest:LoginRequest): Response<LoginResponse>? {
return UserApi.getApi()?.loginUser(loginRequest = loginRequest)
}
}

Now we will crate the view model file.

LoginViewModel.kt

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

val userRepo = UserRepository()
val loginResult: MutableLiveData<BaseResponse<LoginResponse>> = MutableLiveData()

fun loginUser(email: String, pwd: String) {

loginResult.value = BaseResponse.Loading()
viewModelScope.launch {
try {

val loginRequest = LoginRequest(
password = pwd,
email = email
)
val response = userRepo.loginUser(loginRequest = loginRequest)
if (response?.code() == 200) {
loginResult.value = BaseResponse.Success(response.body())
} else {
loginResult.value = BaseResponse.Error(response?.message())
}

} catch (ex: Exception) {
loginResult.value = BaseResponse.Error(ex.message)
}
}
}
}

we will create our design part first

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">

<ProgressBar
android:id="@+id/prgbar"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/txtLay_emailAdd"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="Email"
app:endIconMode="clear_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.4"
app:layout_constraintWidth_percent="0.9">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/txtInput_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLength="20" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/txtLay_pass_signup"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="PASSWORD"
app:endIconDrawable="@drawable/ic_lock"
app:endIconMode="password_toggle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtLay_emailAdd"
app:layout_constraintWidth_percent="0.9">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/txt_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:maxLength="15" />
</com.google.android.material.textfield.TextInputLayout>

<Button
android:id="@+id/btn_login"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginEnd="@dimen/_2sdp"
android:text="@string/login"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btn_register"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

SessionManager.kt

object SessionManager {

const val USER_TOKEN = "user_token"

/**
* Function to save auth token
*/
fun saveAuthToken(context: Context, token: String) {
saveString(context, USER_TOKEN, token)
}

/**
* Function to fetch auth token
*/
fun getToken(context: Context): String? {
return getString(context, USER_TOKEN)
}

fun saveString(context: Context, key: String, value: String) {
val prefs: SharedPreferences =
context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE)
val editor = prefs.edit()
editor.putString(key, value)
editor.apply()

}

fun getString(context: Context, key: String): String? {
val prefs: SharedPreferences =
context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE)
return prefs.getString(this.USER_TOKEN, null)
}

fun clearData(context: Context){
val editor = context.getSharedPreferences(context.getString(R.string.app_name), Context.MODE_PRIVATE).edit()
editor.clear()
editor.apply()
}
}

now we will code for the main activity.

MainActivity.kt

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<LoginViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val token = SessionManager.getToken(this)
if (!token.isNullOrBlank()) {
navigateToHome()
}

viewModel.loginResult.observe(this) {
when (it) {
is BaseResponse.Loading -> {
showLoading()
}

is BaseResponse.Success -> {
stopLoading()
processLogin(it.data)
}

is BaseResponse.Error -> {
processError(it.msg)
}
else -> {
stopLoading()
}
}
}

binding.btnLogin.setOnClickListener {
doLogin()

}

binding.btnRegister.setOnClickListener {
doSignup()
}

}

private fun navigateToHome() {
val intent = Intent(this, LogoutActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(FLAG_ACTIVITY_NO_HISTORY)
startActivity(intent)
}

fun doLogin() {
val email = binding.txtInputEmail.text.toString()
val pwd = binding.txtPass.text.toString()
viewModel.loginUser(email = email, pwd = pwd)

}

fun doSignup() {

}

fun showLoading() {
binding.prgbar.visibility = View.VISIBLE
}

fun stopLoading() {
binding.prgbar.visibility = View.GONE
}

fun processLogin(data: LoginResponse?) {
showToast("Success:" + data?.message)
if (!data?.data?.token.isNullOrEmpty()) {
data?.data?.token?.let { SessionManager.saveAuthToken(this, it) }
navigateToHome()
}
}

fun processError(msg: String?) {
showToast("Error:" + msg)
}

fun showToast(msg: String) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}


}

now We design our logout screen.

activity_logout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.LogoutActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome user"
android:textSize="@dimen/_16sdp"
android:textStyle="bold|italic"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="@dimen/_70sdp"
/>
<Button
android:id="@+id/btn_logout"
android:text="Logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

We need to code for the logout screen.

LogoutActivity.kt

class LogoutActivity : AppCompatActivity() {

private lateinit var binding: ActivityLogoutBinding

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

binding.btnLogout.setOnClickListener {
SessionManager.clearData(this)
val intent = Intent(this, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
startActivity(intent)
}
}
}

That's it.

Login and Logout screen.

--

--

Harshita Bambure

Android Developer || WomenTech Global Ambassador at WomenTech Network. || Yoga Teacher || Member @WTM .