Login API with retrofit and MVVM with auto-login in android kotlin.
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.