Jetpack Compose全局监听用户登录状态自动退出登录

前言

Jetpack Compose一般我们同时也会使用路由进行页面跳转,接口请求的时候,我们想用户信息失效的时候,退出到登录页面,直接在页面监听,就太麻烦和逻辑冗余了。

推荐的做法是

全局状态管理 + 统一网络拦截 + 导航控制

全局状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import androidx.compose.runtime.mutableStateOf

class AuthManager private constructor() {
val isLoggedIn = mutableStateOf(true)

fun logout() {
isLoggedIn.value = false
}

fun login() {
isLoggedIn.value = true
}

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

fun getInstance(): AuthManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: AuthManager().also { INSTANCE = it }
}
}
}
}

网络拦截

接口拦截器中改变状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.gson.Gson
import com.xhkjedu.zxs_android.common.CommonData
import com.xhkjedu.zxs_android.model.ResultVoNoData
import com.xhkjedu.zxs_android.utils.common.ZSPUtil
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody


class HeaderInterceptor : Interceptor {
val TAG: String = "HeaderInterceptor"

override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
try {
val mBuilder = original.newBuilder()
if (CommonData.Authorization != "") {
mBuilder.header("Authorization", CommonData.Authorization)
}

val request = mBuilder
.method(original.method, original.body)
.build()

val response = chain.proceed(request)
// 获取响应头
val headers = response.headers
// 处理响应头
headers.forEach { header ->
if (header.first.lowercase() == "authorization") {
CommonData.Authorization = header.second
ZSPUtil.putString("Authorization", header.second)
Handler(Looper.getMainLooper()).post {
AuthManager.getInstance().login()
}

}
}

return handleResponse(response)
} catch (_: Exception) {
return chain.proceed(original)
}
}

/**
* 响应统一处理
* @param response 原始响应
* @return 处理后的响应(可修改响应内容或直接返回原始响应)
*/
private fun handleResponse(response: Response): Response {
val statusCode = response.code
if (statusCode == 200) {
val body = response.body
body?.let {
val contentType = it.contentType()?.toString() ?: ""
if (contentType.contains("application/json")) {
val responseStr = it.string()
try {
val resultVoNoData =
Gson().fromJson(
responseStr,
ResultVoNoData::class.java
)
if (resultVoNoData.code == 2) {
Handler(Looper.getMainLooper()).post {
AuthManager.getInstance().logout()
}
}
} catch (_: Exception) {
Log.e(TAG, "handleResponse: 解析失败")
}
val newBody: ResponseBody = responseStr
.toResponseBody(it.contentType())
return response.newBuilder().body(newBody).build()
}
}

}
return response
}
}

设置拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (!::commonRetrofit.isInitialized) {
val client = getUnsafeOkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.addInterceptor(HeaderInterceptor())
.build();

commonRetrofit = Retrofit.Builder()
.baseUrl(ConfigData.apiUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

导航控制

路由配置中监听登录状态变化,退出登录。

通过监听状态,只有状态变化的时候才会退出登录,防止多次触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Composable
fun AppNavigation() {
val navController = rememberNavController()

val authManager = AuthManager.getInstance()
val isLoggedIn by authManager.isLoggedIn
// 如果状态变为未登录,强制跳转到登录页
LaunchedEffect(isLoggedIn) {
if (!isLoggedIn) {
navController.navigate(AppScreen.ScreenLogin.route) {
popUpTo(navController.graph.startDestinationId) { inclusive = true }
launchSingleTop = true
}
}
}

CompositionLocalProvider(LocalNavController provides navController) {
NavHost(
navController = navController,
startDestination = "root",
// 添加共享元素动画
enterTransition = {
slideIntoContainer(
AnimatedContentTransitionScope.SlideDirection.Left
)
},
exitTransition = {
slideOutOfContainer(
AnimatedContentTransitionScope.SlideDirection.Right
)
}
) {
rootNavGraph(navController)
}
}
}

核心代码

1
2
3
4
5
6
7
8
9
10
11
val authManager = AuthManager.getInstance()
val isLoggedIn by authManager.isLoggedIn
// 如果状态变为未登录,强制跳转到登录页
LaunchedEffect(isLoggedIn) {
if (!isLoggedIn) {
navController.navigate(AppScreen.ScreenLogin.route) {
popUpTo(navController.graph.startDestinationId) { inclusive = true }
launchSingleTop = true
}
}
}