ViewBinding、DataBinding、ViewModel上手
未经授权,不得转载
open api:wanandroid
1. 开启 ViewBinding、DataBinding特性
// app#build.gradle#android
buildFeatures {
dataBinding true
viewBinding true
}
2. 添加依赖
// app#build.gradle#dependencies
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:latest'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:latest'
3. ViewBinding、DataBindg 上手
- fragment_simple.xml,快捷键
alt+enter
可以快速转换为data binding layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="lga.app.wahome.api.model.User" />
<variable
name="user"
type="User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="simple ViewBinding + DataBinding" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@{user.name}"
tools:text="android" />
</LinearLayout>
</layout>
- SimpleFragment.kt
/**
* simple ViewBinding + DataBinding
*/
class SimpleFragment : Fragment() {
companion object {
const val TAG = "fragment_home"
@JvmStatic
fun newInstance() = SimpleFragment()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// ViewBinding
val binding = FragmentSimpleBinding.inflate(inflater, container, false)
binding.user = User("Kotlin")
binding.lifecycleOwner = this
return binding.root
}
}
4. 进阶
- 定义数据源:IArticleListDataSource.kt
interface IArticleListDataSource {
suspend fun reqHomeArticleList(page: Int, count: Int): ApiResponse<ArticleList<Article>>
}
data class ApiResponse<T>(
val `data`: T?,
val errorCode: Int,
val errorMsg: String
) {
fun isSuccess() = errorCode == 0
}
实现数据源,从网络获取数据:ArticleListRemoteDataSource.kt
package lga.app.wahome.home.vm
import lga.app.wahome.api.RetrofitCreator
import lga.app.wahome.api.WanApiService
class ArticleListRemoteDataSource(apiCreator: RetrofitCreator) : IArticleListDataSource {
private var service: WanApiService = apiCreator.wanApiService
override suspend fun reqHomeArticleList(page: Int, count: Int) =
service.reqHomeArticleList(page)
}
package lga.app.wahome.api
import lga.app.wahome.api.model.Article
import lga.app.wahome.api.model.ArticleList
import retrofit2.http.*
interface WanApiService {
companion object {
const val BASE_URL = "https://www.wanandroid.com/"
}
/**
* 首页文章列表
* https://www.wanandroid.com/article/list/0/json
*/
@GET("article/list/{page}/json")
suspend fun reqHomeArticleList(@Path("page") page: Int): ApiResponse<ArticleList<Article>>
}
- 数据源封装到数据仓库:ArticleListRepository.kt
package lga.app.wahome.home.vm
class ArticleListRepository(private val dataSource: IArticleListDataSource) {
suspend fun reqHomeArticleList(page: Int = 0, count: Int = 15) =
dataSource.reqHomeArticleList(page, count)
}
- ViewModel 实现:ArticleListViewModel.kt
package lga.app.wahome.home.vm
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import lga.app.wahome.api.model.Article
import lga.app.wahome.home.ui.HomeFragment
class ArticleListViewModel(private val repo: ArticleListRepository) : ViewModel() {
private val _articles = MutableLiveData<List<Article>>()
val articles: LiveData<List<Article>> = _articles
fun requestData(page: Int = 0, count: Int = 15) {
viewModelScope.launch {
val response = repo.reqHomeArticleList(page)
if (response.isSuccess()) {
val dataPack = response.data
if (dataPack != null) {
val data = dataPack.datas
if (data != null) {
Log.d(HomeFragment.TAG, "${data.size}")
data.let { _articles.value = it }
} else {
Log.d(HomeFragment.TAG, "no data")
}
} else {
Log.d(HomeFragment.TAG, "no data")
}
} else {
Log.d(HomeFragment.TAG, "error")
}
}
}
}
- 自定义 Factory,方便传参:ArticleListVMFactory.kt
package lga.app.wahome.home.vm
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
class ArticleListVMFactory(private val repo: ArticleListRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
ArticleListViewModel(repo) as T
}
- ArticleListInjection.kt
package lga.app.wahome.home.vm
import lga.app.wahome.api.RetrofitCreator
object ArticleListInjection {
private val dataSource = ArticleListRemoteDataSource(RetrofitCreator)
private val repo = ArticleListRepository(dataSource)
private val vmFactory = ArticleListVMFactory(repo)
fun provideRepo(): IArticleListDataSource = dataSource
fun provideVMFactory() = vmFactory
}
写到这里,ViewModel 骨架完成,下面写 UI
- fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="ViewBinding + coroutine + ViewModel" />
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="0" />
</LinearLayout>
- HomeFragment.kt
package lga.app.wahome.home.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import kotlinx.coroutines.*
import lga.app.wahome.databinding.FragmentHomeBinding
import lga.app.wahome.home.vm.ArticleListInjection
import lga.app.wahome.home.vm.ArticleListViewModel
/**
* ViewBinding + coroutine + ViewModel
*/
class HomeFragment : Fragment(), CoroutineScope by MainScope() {
companion object {
const val TAG = "fragment_home"
@JvmStatic
fun newInstance() = HomeFragment()
}
private lateinit var binding: FragmentHomeBinding
private lateinit var tv: TextView
private val viewModel: ArticleListViewModel by viewModels { ArticleListInjection.provideVMFactory() }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// ViewBinding
binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
atUISetup()
atVMSetup()
doRequestData()
}
private fun atUISetup() {
tv = binding.tv
}
private fun atVMSetup() {
viewModel.articles.observe(viewLifecycleOwner) {
tv.text = it[0].title
}
}
private fun doRequestData() {
viewModel.requestData(0)
}
}
5. 总结
上文给出了 simple ViewBinding + DataBinding,ViewBinding + coroutine + ViewModel
的示例,而 DataBinding + coroutine + ViewModel,ViewBinding + DataBinding + coroutine + ViewModel
的示例大同小异。此外,也不喜欢把业务逻辑与 xml 布局耦合到一块,如需了解全部示例,请点击传送门。
未经授权,不得转载