You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android MVVM架构下如何通过Repository存储登录AccessToken到SharedPreference

在MVVM架构的Repository中存储AccessToken到SharedPreference

我正在对接实时API,登录接口返回了AccessToken。采用MVVM架构,通过Repository处理数据,希望将该AccessToken存储到SharedPreference中,后续作为其他API的请求头使用,但不清楚如何通过Repository实现存储操作。以下是相关代码:

包结构

当前使用的MVVM架构

Repository类

class OpusRepoImpl private constructor() : OpusRepository {

private val mLivedata: MutableLiveData<LoginPojo> = MutableLiveData()
private var rInstance: RetrofitInstance = RetrofitInstance()

override fun cLoginResponse(
    context: Context,
    username: String,
    password: String
): MutableLiveData<LoginPojo> {

    val apiServices = rInstance.getRetrofitInstance()?.create(ApiServices::class.java)
    val requestBody = LoginBody(username, password)

    val clientId = "myclientid"
    val clientSecret =
        "myClientSecrete"
    val grantType = "password"
    val call = apiServices?.getUser(clientId, clientSecret, grantType, requestBody)

    try {
        call?.enqueue(object : Callback<LoginPojo> {
            override fun onResponse(
                call: Call<LoginPojo>,
                response: Response<LoginPojo>
            ) {
                if (!response.isSuccessful) {
                    Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show()
                    return
                }
                val loginResponse = response.body()
                loginResponse?.access_token

                //Here i want to save the login access token into sharedpreference

                mLivedata.postValue(loginResponse!!)
            }

            override fun onFailure(call: Call<LoginPojo>, t: Throwable) {
                Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show()
            }
        })
    } catch (e: Exception) {
        e.printStackTrace()
        Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show()
    }
    return mLivedata
}

companion object {
    private var instance: OpusRepoImpl? = null

    fun getInstance(): OpusRepoImpl {
        return instance ?: synchronized(this) {
            instance ?: OpusRepoImpl().also { instance = it }
        }
    }
}
}

Login Fragment

class LoginFragment : Fragment(), OnClickListener {


private lateinit var binding: FragmentLoginBinding

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    binding = FragmentLoginBinding.inflate(inflater, container, false)
    return binding.root
}


override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    setOnclicklistener()
}

private fun setOnclicklistener() {
    binding.btnLogin.setOnClickListener(this)
}

companion object {

    @JvmStatic
    fun newInstance() =
        LoginFragment().apply {
            arguments = Bundle().apply {

            }
        }
}

override fun onClick(v: View) {

    when (v.id) {
        R.id.btnLogin -> {
            val email = binding.edEmail.text.toString()
            val pass = binding.edPassword.text.toString()

            if (isValid()) {
                loginCheck(email, pass)
            }
        }

    }
}

private fun loginCheck(email: String, pass: String) {
    //val sharedPreferences = requireContext().getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
    val loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java]

    loginViewModel.loginResponse(requireContext(), email, pass).observe(viewLifecycleOwner, Observer { loginPojo ->

        val accessToken = loginPojo.access_token
        if (accessToken.isNotEmpty()){
            Navigation.findNavController(requireView()).navigate(R.id.homepage)
        }
    })
}

private fun isValid(): Boolean {

    binding.edEmail.error = null
    binding.edPassword.error = null

    var isValid = true
    val email = binding.edEmail.toString()
    val pass = binding.edPassword.toString()

    if (email.isEmpty()) {
        isValid = false
        binding.edEmail.error = "Enter Your Email"
    } else {
        val emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+"
        if (!email.matches(emailPattern.toRegex())) {
            binding.edEmail.error = "Enter a Valid Email"
            isValid = false
        }
    }

    if (pass.isEmpty()) {
        isValid = false
        binding.edPassword.error = "Enter Your Password"
    } else {
        if (pass.length < 8) {
            binding.edPassword.error = "Your pass was at-least 8 character"
            isValid = false
        }
    }

    return isValid
}}

Login ViewModel

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

private val loginRepo = OpusRepoImpl.getInstance()

fun loginResponse(
    context:Context,
    username: String,
    password: String
): MutableLiveData<LoginPojo> {
    return loginRepo.cLoginResponse(context, username, password)
}}

解决方案

1. 封装SharedPreference工具类

先实现一个独立的SP工具类,统一处理Token的存储、读取和清除,避免Repository直接操作SP,降低耦合度:

object PreferenceManager {
    private const val PREF_NAME = "AuthPrefs"
    private const val KEY_ACCESS_TOKEN = "access_token"

    fun saveAccessToken(context: Context, token: String) {
        val sharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        with(sharedPref.edit()) {
            putString(KEY_ACCESS_TOKEN, token)
            apply()
        }
    }

    fun getAccessToken(context: Context): String? {
        val sharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        return sharedPref.getString(KEY_ACCESS_TOKEN, null)
    }

    fun clearAccessToken(context: Context) {
        val sharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
        with(sharedPref.edit()) {
            remove(KEY_ACCESS_TOKEN)
            apply()
        }
    }
}

2. 在Repository中保存Token

在登录接口的onResponse回调中,调用工具类的方法存储AccessToken:

override fun onResponse(
    call: Call<LoginPojo>,
    response: Response<LoginPojo>
) {
    if (!response.isSuccessful) {
        Toast.makeText(context, "登录失败", Toast.LENGTH_SHORT).show()
        return
    }
    val loginResponse = response.body()
    loginResponse?.access_token?.let { token ->
        // 保存Token到SharedPreference
        PreferenceManager.saveAccessToken(context, token)
    }

    mLivedata.postValue(loginResponse!!)
}

3. 优化:避免Repository持有Context(推荐)

当前Repository通过方法参数获取Context,存在内存泄漏风险,建议初始化时传入Application Context:

  • 修改Repository单例方法:
class OpusRepoImpl private constructor(private val appContext: Context) : OpusRepository {
    // 其他代码不变

    companion object {
        private var instance: OpusRepoImpl? = null

        fun getInstance(appContext: Context): OpusRepoImpl {
            return instance ?: synchronized(this) {
                instance ?: OpusRepoImpl(appContext.applicationContext).also { instance = it }
            }
        }
    }
}
  • 修改ViewModel传递Application Context:
class LoginViewModel(val application: Application) : AndroidViewModel(application) {
    private val loginRepo = OpusRepoImpl.getInstance(application)

    fun loginResponse(
        username: String,
        password: String
    ): MutableLiveData<LoginPojo> {
        return loginRepo.cLoginResponse(username, password)
    }
}
  • 同时修改Repository的cLoginResponse方法,移除Context参数,直接使用成员变量appContext

4. 后续API自动携带Token

通过Retrofit拦截器,自动将Token添加到请求头:

class AuthInterceptor(private val context: Context) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val token = PreferenceManager.getAccessToken(context)

        val authenticatedRequest = token?.let {
            originalRequest.newBuilder()
                .header("Authorization", "Bearer $it")
                .build()
        } ?: originalRequest

        return chain.proceed(authenticatedRequest)
    }
}

在Retrofit初始化时添加该拦截器:

fun getRetrofitInstance(): Retrofit? {
    val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(AuthInterceptor(appContext))
        .build()

    return Retrofit.Builder()
        .baseUrl("YOUR_BASE_URL")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
}

内容的提问来源于stack exchange,提问作者Asikur Rahman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 06:37:02