1
0
Эх сурвалжийг харах

sample: 为ViewModel示例添加更多注释

drake 1 жил өмнө
parent
commit
c4d7c4cde6

+ 6 - 0
sample/src/main/java/com/drake/net/sample/constants/Api.kt

@@ -1,5 +1,9 @@
 package com.drake.net.sample.constants
 
+
+/*
+建议请求路径都写在一个单例类中, 方便查找和替换
+*/
 object Api {
     const val HOST = "http://127.0.0.1:8091"
 
@@ -9,4 +13,6 @@ object Api {
     const val GAME = "/game"
     const val DATA = "/data"
     const val ARRAY = "/array"
+    const val CONFIG = "/config"
+    const val USER_INFO = "/userInfo"
 }

+ 2 - 0
sample/src/main/java/com/drake/net/sample/mock/MockDispatcher.kt

@@ -41,6 +41,8 @@ class MockDispatcher : Dispatcher() {
             Api.GAME -> getRawResponse(R.raw.game)
             Api.DATA -> getRawResponse(R.raw.data)
             Api.ARRAY -> getRawResponse(R.raw.array)
+            Api.USER_INFO -> getRawResponse(R.raw.user)
+            Api.CONFIG -> getRawResponse(R.raw.user)
             else -> MockResponse().setResponseCode(404)
         }
     }

+ 9 - 0
sample/src/main/java/com/drake/net/sample/model/ConfigModel.kt

@@ -0,0 +1,9 @@
+package com.drake.net.sample.model
+
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ConfigModel(
+    var maintain: Boolean = false
+)

+ 12 - 0
sample/src/main/java/com/drake/net/sample/model/UserInfoModel.kt

@@ -0,0 +1,12 @@
+package com.drake.net.sample.model
+
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UserInfoModel(
+    var userId: Int = 0,
+    var username: String = "",
+    var age: Int = 0,
+    var balance: String = ""
+)

+ 11 - 0
sample/src/main/java/com/drake/net/sample/ui/fragment/ViewModelRequestFragment.kt

@@ -4,7 +4,9 @@ import androidx.fragment.app.viewModels
 import com.drake.engine.base.EngineFragment
 import com.drake.net.sample.R
 import com.drake.net.sample.databinding.FragmentViewModelRequestBinding
+import com.drake.net.sample.utils.HttpUtils
 import com.drake.net.sample.vm.UserViewModel
+import com.drake.net.utils.scopeNetLife
 
 class ViewModelRequestFragment :
     EngineFragment<FragmentViewModelRequestBinding>(R.layout.fragment_view_model_request) {
@@ -12,6 +14,8 @@ class ViewModelRequestFragment :
     private val userViewModel: UserViewModel by viewModels() // 创建ViewModel
 
     override fun initView() {
+
+        // 直接将用户信息绑定到视图上
         binding.lifecycleOwner = this
         binding.m = userViewModel
 
@@ -22,5 +26,12 @@ class ViewModelRequestFragment :
     }
 
     override fun initData() {
+
+        scopeNetLife {
+            val configAsync = HttpUtils.getConfigAsync(this)
+            // 经常使用的请求可以封装函数
+            val userInfo = HttpUtils.getUser()
+            configAsync.await() // 实际上在getUser之前就发起请求, 此处只是等待结果, 这就是并发请求
+        }
     }
 }

+ 35 - 0
sample/src/main/java/com/drake/net/sample/utils/HttpUtils.kt

@@ -0,0 +1,35 @@
+package com.drake.net.sample.utils
+
+import com.drake.net.Get
+import com.drake.net.sample.constants.Api
+import com.drake.net.sample.model.ConfigModel
+import com.drake.net.sample.model.UserInfoModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
+
+
+/**
+ * 常用的请求方法建议写到一个工具类中
+ */
+object HttpUtils {
+
+    /**
+     * 获取配置信息
+     *
+     * 本方法需要再调用await()才会返回结果, 属于异步方法
+     */
+    fun getConfigAsync(scope: CoroutineScope) = scope.Get<ConfigModel>(Api.CONFIG)
+
+    /**
+     * 获取用户信息
+     * 阻塞返回可直接返回结果
+     *
+     * @param userId 如果为空表示请求自身用户信息
+     */
+    suspend fun getUser(userId: String? = null) = coroutineScope {
+        Get<UserInfoModel>(Api.USER_INFO) {
+            param("userId", userId)
+        }.await()
+    }
+
+}

+ 23 - 6
sample/src/main/java/com/drake/net/sample/vm/UserViewModel.kt

@@ -8,24 +8,41 @@ import com.drake.net.sample.constants.Api
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.coroutineScope
 
+
+/**
+ * 强烈建议不要封装作用域, 封装异步任务即可, 作用域灵活随用随写(Activity/Fragment都可以写作用域)
+ *
+ * 不要企图把所有逻辑代码都写在ViewModel中就觉得自己会写MVVM了, 给代码换个位置不叫架构设计, 特别是还增加一堆无效代码情况下
+ */
 class UserViewModel : ViewModel() {
 
     // 用户信息
     var userInfo: MutableLiveData<String> = MutableLiveData()
 
+    var updateTime: Long = 0
+
     /**
-     * 拉取用户信息, 会自动通知页面更新, 同时页面销毁会自动取消网络请求
-     * 其包含作用域, 生命周期跟随当前viewModel
-     * scopeNetLife/scopeDialog不推荐写在ViewModel中
+     * 拉取用户信息, 只能监听返回结果, 仅当外部调用对象不在乎返回结果时使用
+     * 会自动通知页面更新: 因为使用LiveData将请求结果回调出去, 建议将该liveData对象直接使用DataBinding绑定到页面上, 就会自动触发UI
+     * 同时页面销毁会自动取消网络请求: 因为他使用`scopeNetLife`. 生命周期跟随当前viewModel
+     *
+     * 本质上我并不推荐将Scope定义在ViewModel中(仅仅换个位置要多写很多代码), 特别是妄图在ViewModel中请求网络却要求更新UI
      */
     fun fetchUserInfo() = scopeNetLife {
         userInfo.value = Get<String>(Api.GAME).await()
+        updateTime = System.currentTimeMillis()
     }
 
-    /** 返回Deferred, 可以灵活使用, 支持并发组合 */
-    fun CoroutineScope.fetchList() = Get<String>(Api.TEST)
+    /**
+     * 非阻塞异步任务
+     *  返回Deferred, 调用await()才会返回结果. 调用即执行任务
+     */
+    fun fetchList(scope: CoroutineScope) = scope.Get<String>(Api.TEST)
 
-    /** 直接返回数据, 会阻塞直至数据返回 */
+    /**
+     * 阻塞异步任务
+     * 直接返回结果
+     */
     suspend fun fetchPrecessData() = coroutineScope {
         val response = Get<String>(Api.TEST).await()
         response + "处理数据"

+ 3 - 0
sample/src/main/res/raw/config.json

@@ -0,0 +1,3 @@
+{
+  "maintain": false
+}

+ 6 - 0
sample/src/main/res/raw/user.json

@@ -0,0 +1,6 @@
+{
+  "userId": 23123132,
+  "username": "test_account",
+  "age": 17,
+  "balance": "123.24"
+}