kotlinauthenticationtokenandroid-preferencesdatastore

Application blink when i try to save token using preferences ( DataStore )


i have a problem here, the problem is about application blinking, it happened when i tried to save login token from API, i am using dataStore preferences to get and set the token. Before i add the preferences into my WelcomeActivity the app run smoothly but when i add the function to get & set the token the app is blank i can't touch the editText and when i close and open the app again it becomes blinking/flashing without stopping, maybe you can help me to find the solution to fix this proble

thank you

this is the error in my app

you can see the detail of the error in this video : https://youtu.be/pGTfE29KzO0

and here is my code

WelcomeActivity.kt :


import android.content.ContentValues.TAG
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import com.example.dicodingsocialmedia.databinding.ActivityWelcomeBinding
import com.example.sosmeddicoding.data.model.ErrorResponse
import com.example.sosmeddicoding.data.model.ResponseLogin
import com.example.sosmeddicoding.data.repo.AuthRepo
import com.example.sosmeddicoding.data.service.ApiConfig
import com.example.sosmeddicoding.ui.auth.RegisterActivity
import com.example.sosmeddicoding.ui.auth.RegisterViewModel
import com.example.sosmeddicoding.ui.auth.RegisterViewModelFactory
import com.example.sosmeddicoding.utils.AuthPreferences
import com.example.sosmeddicoding.utils.CustomEditText
import com.example.sosmeddicoding.utils.dataStore
import com.google.android.material.tabs.TabLayout
import com.google.gson.Gson
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response

class WelcomeActivity : AppCompatActivity() {
    private lateinit var binding: ActivityWelcomeBinding
    private lateinit var tabLayout: TabLayout
    private lateinit var viewPager2: ViewPager2
    private lateinit var authButton: Button
    private lateinit var editText: CustomEditText

    private lateinit var viewModel: WelcomeViewModel
    private lateinit var authPreferences: AuthPreferences

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

        authButton = binding.buttonLogin
        editText = binding.password

        setMyButtonEnable()

        // Check Token
        authPreferences = AuthPreferences.getInstance(application.dataStore)
        lifecycleScope.launch {
            authPreferences.getAuthToken.collect { savedToken ->
                if (savedToken != "") {
                    startActivity(Intent(applicationContext, MainActivity::class.java))
                } else {
                    startActivity(Intent(applicationContext, WelcomeActivity::class.java))
                }
            }
        }

        editText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
            }

            override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
                if (s.toString().length < 8) {
                    editText.error = "Password tidak boleh kurang dari 8 karakter"
                } else {
                    editText.error = null
                }
                setMyButtonEnable()
            }

            override fun afterTextChanged(s: Editable) {
            }
        })
        authButton.setOnClickListener {
            Toast.makeText(
                this@WelcomeActivity,
                editText.text,
                Toast.LENGTH_SHORT
            ).show()
        }


        binding.registerHere.setOnClickListener {
            startActivity(Intent(applicationContext, RegisterActivity::class.java))
        }



        authButton.setOnClickListener {
            showLoading(true)
            val email = binding.email.text.toString()
            val password = binding.password.text.toString()

            val authService = ApiConfig.getApiService()
            val authRepo = AuthRepo(authService)
            viewModel = ViewModelProvider(
                this,
                WelcomeViewModelFactory(authRepo)
            ).get(WelcomeViewModel::class.java)

            lifecycleScope.launch {
                try {
                    val successLogin = viewModel.loginVM(email, password)
                    if (successLogin.error == false && successLogin.loginResult != null) {
                        startActivity(Intent(applicationContext, MainActivity::class.java))
                        val token = successLogin.loginResult.token
                        authPreferences.saveToken(token!!)
                    }
                } catch (e: HttpException) {
                    showLoading(false)
                    val jsonInString = e.response()?.errorBody()?.string()
                    val errorBody = Gson().fromJson(jsonInString, ErrorResponse::class.java)
                    val errorMessage = errorBody.message
                    if (errorMessage != null) {
                        showToast(errorMessage)
                        Toast.makeText(
                            this@WelcomeActivity,
                            "Register Failed, please register again",
                            Toast.LENGTH_LONG
                        ).show()
                    }
                }
            }

        }


    }

    private fun showLoading(isLoading: Boolean) {
        if (isLoading) {
            binding.cardView.visibility = View.VISIBLE
        } else {
            binding.cardView.visibility = View.GONE
        }
    }

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


    private fun setMyButtonEnable() {
        val result = editText.text
        authButton.isEnabled = result != null && result.toString().isNotEmpty()
    }


}

AuthPreferences.kt

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.preferencesDataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = "auth_token")

class AuthPreferences private constructor(private val dataStore: DataStore<Preferences>) {

    private val AUTH_TOKEN_KEY = stringPreferencesKey("token")

    val getAuthToken: Flow<String?> = dataStore.data.map { preferences ->
        preferences[AUTH_TOKEN_KEY] ?: ""
    }

    suspend fun saveToken(token: String) {
        dataStore.edit { auth ->
            auth[AUTH_TOKEN_KEY] = token
        }
    }

    companion object {
        @Volatile
        private var INSTANCE: AuthPreferences? = null

        fun getInstance(dataStore: DataStore<Preferences>): AuthPreferences {
            return INSTANCE ?: synchronized(this) {
                val instance = AuthPreferences(dataStore)
                INSTANCE = instance
                instance
            }
        }
    }
}

ResponseLogin.kt :

import kotlinx.parcelize.Parcelize
import android.os.Parcelable
import com.google.gson.annotations.SerializedName

@Parcelize
data class ResponseLogin(

    @field:SerializedName("loginResult")
    val loginResult: LoginResult? = null,

    @field:SerializedName("error")
    val error: Boolean? = null,

    @field:SerializedName("message")
    val message: String? = null
) : Parcelable

@Parcelize
data class LoginResult(

    @field:SerializedName("name")
    val name: String? = null,

    @field:SerializedName("userId")
    val userId: String? = null,

    @field:SerializedName("token")
    val token: String? = null
) : Parcelable

and here is my logcat : enter image description here


Solution

  • You create a recursion when in WelcomeActivity.onCreate you start a coroutine that starts an activity that starts a coroutine and so on.