作者:512DIDIDI
链接:https://www.jianshu.com/p/1560de5422ca
Google推JetPack已经有一段时间了,伴随之而来的是MVVM架构,使用ViewModel
LiveData
等工具来实现data-binding
。
JetPack中还附带了一个Navigation
,顾名思义,即导航功能,主要目的是用来实现单Activity架构,之前写过一篇文章,是利用fragmentation
来实现单Activity架构,抱着学习的态度,这次的项目采用了Navigation
来实现单Activity
架构。
先附带项目的MVVM架构图:
绿色代表View层
蓝色代表ViewModel层
红色代表Model层
各层之间均是单向依赖,即V层向VM层发起请求,VM层向M层获取数据,再通过LiveData
作为桥梁,V层监听LiveData
数据,数据变化时更新UI。
举个代码例子吧,登录流程:
首先是V层代码:
class LoginFragment : BaseFragment(), View.OnClickListener {
privateval viewModel by lazy {
ViewModelProviders.of(this).get(LoginViewModel::class.java)
}
... ...
overridefun bindView(savedInstanceState: Bundle?, rootView: View) {
observe()
fragmentLoginSignInLayoutSignIn.setOnClickListener(this)
}
overridefun onClick(v: View?) {
when (v?.id) {
R.id.fragmentLoginSignInLayoutSignIn -> {
//调用登录
viewModel.login(
fragmentLoginSignInLayoutAccount.text.toString(),
fragmentLoginSignInLayoutPassword.text.toString()
)
}
... ...
}
}
/**
* 监听数据变化
*/
privatefun observe() {
//登录监听
viewModel.loginLiveData.observe(this, Observer {
if (it.errorCode != 0) {
toast(it.errorMsg)
} else {
Navigation.findNavController(fragmentLoginSignInLayoutSignIn)
.navigate(R.id.action_loginFragment_to_mainFragment)
}
})
//注册监听
viewModel.registerLiveData.observe(this, Observer {
if (it.errorCode != 0) {
toast(it.errorMsg)
} else {
toast("注册成功,返回登录页登录")
getSignInAnimation(fragmentLoginSignUpLayout)
}
})
//loading窗口监听
viewModel.isLoading.observe(this, Observer {
if (it) {
showLoading()
} else {
dismissAllLoading()
}
})
}
}
获取LoginViewModel
的实例,点击登录按钮时,调用其login(userName:String,password:String)
方法,在observe()
方法中获取其LiveData
数据的observe
方法,监听其数据变化,在Observer
匿名类里进行UI的更新。
VM层代码:
class LoginViewModel(application: Application) : BaseViewModel(application) {
/**
* 登录数据
*/
var loginLiveData = MutableLiveData()
/**
* 注册数据
*/
var registerLiveData = MutableLiveData()
/**
* 登出数据
*/
var logOutLiveData = MutableLiveData()
privateval repository = LoginRepository.getInstance(PackRatNetUtil.getInstance())
fun login(username: String, password: String) {
launch {
loginLiveData.postValue(repository.login(username, password))
}
}
fun register(username: String, password: String, repassword: String) {
launch {
registerLiveData.postValue(repository.register(username, password, repassword))
}
}
fun logOut() {
launch {
logOutLiveData.postValue(repository.logOut())
}
}
}
很简单,做的就是实例化LiveData
和repository
数据依赖层,并调用repository
获取数据,最后往里postValue
赋值。我这里包装了一层BaseViewModel
,它继承了AndroidViewModel
,它与普通的ViewModel不同之处在于可以需要传入application
参数,也就是可以获取一个全局context
引用。
abstractclass BaseViewModel(application: Application) : AndroidViewModel(application){
/**
* 加载变化
*/
var isLoading = MutableLiveData()
/**
* 统一协程处理
*/
fun launch(block:suspend() -> Unit) = viewModelScope.launch {
try {
isLoading.value = true
withContext(Dispatchers.IO){
block()
}
isLoading.value = false
}catch (t:Throwable){
t.printStackTrace()
getApplication().toast(t.message)
isLoading.value = false
}
}
}
抽离了一个协程方法,耗时操作统一到IO线程操作,loading
在耗时方法完成时置为false
,通知页面关闭弹窗。
M层代码:
class LoginRepository private constructor(
privateval net: PackRatNetUtil
) {
companionobject {
@Volatile
privatevar instance: LoginRepository? = null
fun getInstance(net: PackRatNetUtil) =
instance ?: synchronized(this) {
instance ?: LoginRepository(net).apply {
instance = this
}
}
}
suspendfun login(username: String, password: String) =
net.fetchLoginResult(username, password)
suspendfun register(username: String, password: String, repassword: String) =
net.fetchRegisterResult(username, password, repassword)
suspendfun logOut() =
net.fetchQuitResult()
}
这里做的事情也很简单,从网络层获取数据,当然,如果需要存放本地数据库,可以如下实现:
class CollectRepository private constructor(
privatevar collectDao: CollectDao,
privatevar net: PackRatNetUtil
) {
companionobject {
@Volatile
privatevar instance: CollectRepository? = null
fun getInstance(collectDao: CollectDao, net: PackRatNetUtil) =
instance ?: synchronized(this) {
instance ?: CollectRepository(collectDao, net).apply {
instance = this
}
}
}
/**
* 获取收藏列表数据
*/
suspendfun getCollects() = try {
net.fetchCollectList()
} catch (t: Throwable) {
t.printStackTrace()
collectDao.getCollectList()
}
/**
* 设置收藏列表存储入数据库
*/
suspendfun setCollects(collects: List) {
collects.forEach {
collectDao.insert(it)
log(content = it.content)
}
}
}
传入本地数据库及网络层的实例,然后依照不同的情况分别获取数据。
class PackRatNetUtil private constructor() {
companionobject {
@Volatile
privatevar instance: PackRatNetUtil? = null
fun getInstance() = instance ?: synchronized(this) {
instance ?: PackRatNetUtil().apply {
instance = this
}
}
}
privateval collectService = ServiceCreator.create(CollectService::class.java)
privateval loginService = ServiceCreator.create(LoginService::class.java)
/**
* 从服务器获取收藏列表
*/
suspendfun fetchCollectList() = collectService.getCollectAsync().await()
/**
* 获取登录结果
*/
suspendfun fetchLoginResult(username: String, password: String) =
loginService.loginAsync(username, password).await()
/**
* 此方法用于retrofit使用 [Call] 的 [Callback] 回调与协程 [await] 的回调相连
* 不过 retrofit 后续提供了[CoroutineCallAdapterFactory],可返回[Deferred]作为回调
* @Deprecated 引入[com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter]包可以使用Deferred作为回调
*/
privatesuspendfun Call.await(): T = suspendCoroutine { continuation ->
enqueue(object : Callback {
overridefun onFailure(call: Call, t: Throwable) {
continuation.resumeWithException(t)
}
overridefun onResponse(call: Call, response: Response) {
val body = response.body()
if (body != null) {
continuation.resume(body)
} else {
continuation.resumeWithException(NullPointerException("response body is null"))
}
}
})
}
}
这里提下,之前retrofit
没有提供coroutines-adapter
依赖包时,不能使用Deferred
作为回调,可重写其Call的await
方法,将协程的resume
方法与resumeWithException
方法与之对应,从而使retrofit
能更好的与协程使用,不过retrofit
后续提供了
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutineVersion"
所以可在其apiService
里使用Deferred
作为回调。
interface LoginService {
@FormUrlEncoded
@POST("user/login")
fun loginAsync(
@Field("username") username: String,
@Field("password") password: String
): Deferred
@FormUrlEncoded
@POST("user/register")
fun registerAsync(
@Field("username") username: String,
@Field("password") password: String,
@Field("repassword") repassword: String
): Deferred
@FormUrlEncoded
@GET("user/logout/json")
fun quitAsync(): Deferred
}
MVVM其核心思路就在于各层之间单向依赖单向交流,不能出现V层直接请求数据等操作。后续有空再写Navigation
实现单Activity
架构的思路。
先贴项目代码:https://github.com/512DIDIDI/PackRat
亲,点这涨工资热文推荐:
1、大厂又有新的开源项目了,赶紧来领取...
2、面试官问我:一个 TCP 连接可以发多少个 HTTP 请求?我竟然回答不上来...
3、程序员疑似出bug被吊打!菲律宾的高薪工作机会了解一下?
4、“一键脱衣”的DeepNude下架后,我在GitHub上找到它涉及的技术
5、原生Android开发的路该怎么走
6、太厉害了,终于有人能把TCP/IP 协议讲的明明白白了
7、腾讯开源超实用的UI轮子库,我是轮子搬运工
8、腾讯新开源一吊炸天神器—零反射全动态Android插件框架正式开源
喜欢 就关注吧,欢迎投稿!