3.4.1 viewmodel
3.4.1.1 RxViewModel
abstract class RxViewModel(private val schedulerProvider: SchedulerProvider) : ViewModel() { var jobs = mutableListOf() fun launch(code: suspend CoroutineScope.() -> Unit) { jobs.add(coroutineLaunch(schedulerProvider.ui()) { code.invoke(this) }) } fun launchIo(code: suspend CoroutineScope.() -> Unit) { jobs.add(coroutineLaunch(schedulerProvider.io()) { code.invoke(this) }) } override fun onCleared() { super.onCleared() jobs.forEach { it.cancel() } } }
这里有一些协程代码,还有点不太懂。
3.4.1.2 BaseViewModel
open class BaseViewModel ( schedulerProvider: SchedulerProvider ) : RxViewModel(schedulerProvider) { val progress = ObservableField(false) val isRefreshing = ObservableField (false) val isError = ObservableField (false) val errMsg = ObservableField ("") }
添加一些状态代码如加载,进度条,错误以及错误信息。都通过ObservableField来定义,具体使用可以通过xml查看,以后还要再细品。
3.4.2 adapter
3.4.2.1 BaseBindableAdapter
interface BaseBindableAdapter{ fun setHeader(items: T) {} fun setData(items: List ) {} fun setFooter(items: T) {} fun bind(data: T) {} }
3.4.2.2 GenericAdapter
abstract class GenericAdapter : RecyclerView.Adapter, BaseBindableAdapter { var listItems: List constructor(listItems: List) { this.listItems = listItems notifyDataSetChanged() } constructor() { listItems = emptyList() notifyDataSetChanged() } override fun setData(items: List) { this.listItems = items notifyDataSetChanged() } // TODO: To add header? override fun setHeader(items: DATA) { (this.listItems as MutableList).add(items) notifyItemInserted(0) } // TODO: To add footer? override fun setFooter(items: DATA) { (this.listItems as MutableList).add(items) notifyItemInserted(this.listItems.size - 1) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return getViewHolder( DataBindingUtil.inflate(LayoutInflater.from(parent.context) , viewType , parent , false)!!) } @SuppressWarnings("Unchecked cast") override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { (holder as? BaseBindableAdapter)?.bind(listItems[position]) } override fun getItemCount(): Int { return listItems.size } override fun getItemViewType(position: Int): Int { return getLayoutId(position, listItems[position]) } protected abstract fun getLayoutId(position: Int, obj: DATA): Int // TODO: Use generic ViewDataBinding abstract fun getViewHolder(viewBinding: ViewDataBinding): RecyclerView.ViewHolder }
3.4.3 数据绑定相关
CardViewBinding、ListBiding、ProgressBinding以及ViewBiding
同时添加了几个Ext类
object ListBinding { @SuppressLint(value = ["PrivateResource", "UNCHECKED_CAST"]) @BindingAdapter(value = ["list:isGrid", "list:spanCount", "list:orientation", "list:isReversed"], requireAll = false) @JvmStatic // TODO: Receive generic ViewDataBinding as args fun RecyclerView.initAdapter(isGrid: Boolean = false, spanCount: Int = 0, orientation: Int = 0, isReversed: Boolean = false) { try { if (isGrid) setupGridLayoutManager(spanCount, orientation, isReversed) else setupLinearLayoutManager(orientation, isReversed) } catch (e: Exception) { e.printStackTrace() } } @SuppressLint(value = ["PrivateResource", "UNCHECKED_CAST"]) @BindingAdapter(value = ["list:layoutId", "list:viewType"], requireAll = false) @JvmStatic fun RecyclerView.initViewHolder(layoutId: Int, viewType: Int?) { try { adapter = object : GenericAdapter() { override fun getLayoutId(position: Int, obj: DATA): Int { return layoutId } // TODO: Refactor to generic instead of using when condition override fun getViewHolder(viewBinding: ViewDataBinding): RecyclerView.ViewHolder { return PostViewHolder(viewBinding as PostItemBinding) } } } catch (e: Exception) { e.printStackTrace() } } @SuppressLint(value = ["PrivateResource", "UNCHECKED_CAST"]) @BindingAdapter(value = ["list:items"], requireAll = false) @JvmStatic fun RecyclerView.initData(items: List?) { try { if (adapter is GenericAdapter<*>) { (adapter as GenericAdapter).setData(items ?: emptyList()) } } catch (e: Exception) { e.printStackTrace() } } @SuppressLint(value = ["PrivateResource", "UNCHECKED_CAST"]) @BindingAdapter(value = ["list:items"], requireAll = false) @JvmStatic fun RecyclerView.initData(items: Set?) { try { if (adapter is GenericAdapter<*>) { (adapter as GenericAdapter).setData(items?.toList() ?: emptyList()) } } catch (e: Exception) { e.printStackTrace() } }
将数据显示和Databinding代码混合在一起,可以考虑分开。因为这样我就必须要开始界面PostViewHolder、以及PostItemBinding的编写(暂时放在后面)。
3.4.4 liveData相关
LiveEvent以及SingleLiveEvent
其他没有用到的暂时不理会,需要时再迁移过来。
3.5.1 基础类
BaseActivity
open class BaseActivity : AppCompatActivity(), ToolbarListener { override fun onSupportNavigateUp(): Boolean { finish() return true } override fun onBackPressed() { super.onBackPressed() overridePendingTransition(R.anim.slide_up, R.anim.slide_down) } override fun setupToolbar(toolbar: Toolbar) { setSupportActionBar(toolbar) } override fun updateTitleToolbar(newTitle: String) { supportActionBar?.apply { setDisplayHomeAsUpEnabled(true) title = newTitle subtitle = "" } } }
BaseActivity只是增加了一些动画和Toolbar标题,不过这个在我的这个版本里面没有调通。
BaseUserActionListener
interface BaseUserActionListener { fun onRefresh() }
用于加载页面的接口。
3.5.2 MainActivity
MainActivity使用的是navigation组件中的Fragment跳转管理(不知对不对,我暂时也只是调通了,没有仔细研究)。
class MainActivity : BaseActivity() { private val viewBinding: ActivityMainBinding by lazy { DataBindingUtil.setContentView(this, R.layout.activity_main) } private lateinit var mNavHost: NavHostFragment private lateinit var mNavController: NavController private lateinit var appBarConfiguration: AppBarConfiguration override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //初始化数据绑定 viewBinding.executePendingBindings() setupNavController() setupAppBar() if (::mNavController.isInitialized && ::appBarConfiguration.isInitialized) { setupActionBar(mNavController, appBarConfiguration) } updateTitleToolbar("kivy") } override fun onBackPressed() { if (::mNavHost.isInitialized) { val fragmentsSize = mNavHost.childFragmentManager.fragments.size if (fragmentsSize >= 1) { super.onBackPressed() } else { findNavController(R.id.navHostFragment).navigateUp(appBarConfiguration) } } } override fun onSupportNavigateUp(): Boolean { return if (::mNavHost.isInitialized) { findNavController(R.id.navHostFragment).navigateUp(appBarConfiguration) } else { false } } private fun setupNavController() { mNavHost = supportFragmentManager .findFragmentById(R.id.navHostFragment) as NavHostFragment? ?: return mNavController = mNavHost.navController } private fun setupAppBar() { appBarConfiguration = AppBarConfiguration( setOf(R.id.postFragment), null ) } private fun setupActionBar( navController: NavController, appBarConfiguration: AppBarConfiguration ) { setupToolbar(viewBinding.toolbar.toolbar) setupActionBarWithNavController(navController, appBarConfiguration) } }
同时要将布局文件以及navigation的跳转xml迁移过来。样式,颜色,名称都不是重点可以直接复制过来。
fragment指到navigation中的NavHostFragment,具体的操作在home_nav_graph中。
由于实际操作和我分析代码是不一样的,所以这里要将一些Fragment,layout文件先添加上来。
post_fragment,postdetail_fragment布局文件,以及PostFragment,PostDetailFragment两个Fragment以及前面说过的PostItem类都要添加上。
在此过程中还有一些其他的布局文件也一道迁移过来。
3.5.3 PostFragment 重头大戏
围绕PostFragment,其实还有三个关键类Adapter,具体的Item使用的ViewHolder,数据相关的ViewModel。我们可以沿着这个思路一步一步分析下去。
3.5.2.1 adapter
adapter在ListBinding中直接生成过,直接继承并且是object类型。
@SuppressLint(value = ["PrivateResource", "UNCHECKED_CAST"]) @BindingAdapter(value = ["list:layoutId", "list:viewType"], requireAll = false) @JvmStatic fun RecyclerView.initViewHolder( layoutId: Int, viewType: Int? ) = try { adapter = object : GenericAdapter() { override fun getLayoutId(position: Int, obj: DATA): Int { return layoutId } // TODO: Refactor to generic instead of using when condition override fun getViewHolder(viewBinding: ViewDataBinding): RecyclerView.ViewHolder { return PostViewHolder(viewBinding as PostItemBinding) } } } catch (e: Exception) { e.printStackTrace() }
3.5.2.2 PostViewHolder
class PostViewHolder(val binding: PostItemBinding) : RecyclerView.ViewHolder(binding.root), BaseBindableAdapter{ override fun bind(data: PostItem) { binding.apply { item = data root.setOnClickListener { // val toPostDetail = PostFragmentDirections.toPostDetailAction( // data // ) // // it.findNavController().navigate(toPostDetail) } executePendingBindings() } } }
由于使用了数据绑定,就比较简单了,这里添加的单击事件不在此次考虑中,所以注释了。
3.5.2.3 ViewModel
class PostViewModel( private val appRepository: AppRepository, schedulerProvider: SchedulerProvider ) :BaseViewModel(schedulerProvider){ val keywords = Channel(Channel.UNLIMITED) var postListSet = MutableSetObservableField () /* * We use LiveEvent to publish "states" * No need to publish and retain any view state */ private val _states = LiveEvent () val states: LiveData get() = _states.toSingleEvent() fun getPosts() { _states.value = LoadingState launch { try { val posts = appRepository.getPostsAsync().await() _states.value = PostListState.from(posts!!) } catch (error: Throwable) { _states.value = ErrorState(error) } } } fun searchPosts(query: String) { if (query.isNotBlank()) { _states.value = LoadingState launch { try { val posts = appRepository.searchPostsAsync(query).await() _states.value = PostListState.from(posts!!) } catch (error: Throwable) { _states.value = ErrorState(error) } } } } }
viewModel除了引用repository中的方法外增加了一些状态信息,如LoadingState,ErrorState,以及正确的PostListState。这里有几个问题,由于使用的是sealed类,真的使用PostListState好吗?如果有多个Model呢?是不是要写多个?
State类,以前没有迁移过来。
/** * Abstract State */ sealed class State /** * Generic Loading State */ object LoadingState : State() /** * Generic Error state * @param error - caught error */ data class ErrorState(val error: Throwable) : State() data class PostListState( val list: List) : State() { companion object { fun from(list: List ): PostListState { return with(list) { when { // TODO: @mochadwi Move this into strings instead isEmpty() -> error("There's an empty post instead, please check your keyword") else -> PostListState(this) } } } } }
3.5.2.4 PostFragment
创建布局
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { viewBinding = PostFragmentBinding.inflate(inflater, container, false) .apply { listener = this@PostFragment vm = viewModel } return viewBinding.root }
拉取数据
private fun pullToRefresh() { viewModel.apply { isRefreshing.set(true) if (::onLoadMore.isInitialized) onLoadMore.resetState() getPosts() } }
根据状态处理数据
private fun setupObserver() = with(viewModel) { // Observe ComposeState states.observe(viewLifecycleOwner, Observer { state -> state?.let { when (state) { is LoadingState -> showIsLoading() is PostListState -> { showCategoryItemList( posts = state.list.map { PostItem.from(it) }) } is ErrorState -> showError(state.error) } } }) coroutineLaunch(Main) { keywords.consumeEach { searchPosts(it) } } }
当state为PostListState时,有一个转换过程,将域数据Post转为了PostItem。
其他代码基本上是对以上代码的补充。排除编译错误后运行。
运行代码,结果报错:
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'xyz.wayhua.kivy101.ui.main.fragment.PostViewModel'. Check your definitions! at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:247)
其原因是引用viewmodel是通过by viewModel
private val viewModel by viewModel()
要将viewmodel配置到module中。
3.5.4 di 补充
val viewModelModule = module { viewModel { PostViewModel(get(), get()) } } val allModules = listOf(rxModule, roomModule, viewModelModule,remoteDatasourceModule, repoModule)
构造函数中的get(),get(),其实是告诉我们这里有两个参数,都必须在module中配置。
总体来说,这一次将代码进行了一次清理,同时结构更加清晰。仍然有很多问题,前面就说过如ListBinding直接指定Adapter,虽然少了一个类,但是确实也不太方便,现在只有一个页面,如果有非常多的页面是否要同时修改那个地方,如果有多种viewholder要显示呢?
还有就是结合以前编写程序的习惯,我更倾向于职责分离。这个会有深挖该源码以后,进一步深入。还有一个问题是中国人的习惯,上拉刷新,下拉加载更多,怎么处理。这里的页面是一次性加载100条,分页怎么办?
还有数据只是个补充,主要用途可能就是fts,没有起到缓存数据的作用。
在Status.kt文件中引用了Post类,如下
data class PostListState( val list: List) : State() { companion object { fun from(list: List ): PostListState { return with(list) { when { // TODO: @mochadwi Move this into strings instead isEmpty() -> error("There's an empty post instead, please check your keyword") else -> PostListState(this) } } } } }
这是个seal类,不能扩展,但是每次都这样编写也是麻烦,现在是PostListState,如果还有其他的ListState呢,仍然是要这样编写,这就是一个大麻烦。
因此我编写一个SuccesState类,通过泛型来直接替换掉Post,这开始只是一个设想。
data class SuccessState(val data: List ) : State() { companion object { fun from(data: List ): SuccessState { return with(data) { when { isEmpty() -> error("不能为空") else -> SuccessState(this) } } } } }
如上编写,结果编译通过了。于是我就有一个大胆的想法,替换掉PostListState。
由于前面代码本来就是可以运行的。重构最好做到一次重构一个地方,现在是替换掉PostListState,那最好的办法就是直接注释掉PostListState类,然后编译,将所有的报错一一改掉,再看运行效果,如果没问题,那就表示重构成功了。
PostFragment中要替换后的代码
private fun setupObserver() = with(viewModel) { // Observe ComposeState states.observe(viewLifecycleOwner, Observer { state -> state?.let { when (state) { is LoadingState -> showIsLoading() is SuccessState<*> -> { showCategoryItemList( posts = state.data.map { PostItem.from(it as Post ) }) } is ErrorState -> showError(state.error) } } }) coroutineLaunch(Main) { keywords.consumeEach { searchPosts(it) } } }
SuccessState<*> 表示,里面代码要强制转换。
PostViewModel中要同样替换为
fun getPosts() { _states.value = LoadingState launch { try { val posts = appRepository.getPostsAsync().await() _states.value = SuccessState.from(posts!!) } catch (error: Throwable) { _states.value = ErrorState(error) } } }
fun searchPosts(query: String) { if (query.isNotBlank()) { _states.value = LoadingState launch { try { val posts = appRepository.searchPostsAsync(query).await() _states.value = SuccessState.from(posts!!) } catch (error: Throwable) { _states.value = ErrorState(error) } } } }
再次运行,发现结果和原来一模一样。