Kotlin学习之开源代码分析、重构(二)

3.4 MVVM

3.4.1 viewmodel

3.4.1.1 RxViewModel

abstractclassRxViewModel(privatevalschedulerProvider:SchedulerProvider) :ViewModel() {

varjobs=mutableListOf()

funlaunch(code:suspendCoroutineScope.()->Unit) {

jobs.add(coroutineLaunch(schedulerProvider.ui()) {code.invoke(this) })

   }

funlaunchIo(code:suspendCoroutineScope.()->Unit) {

jobs.add(coroutineLaunch(schedulerProvider.io()) {code.invoke(this) })

   }

overridefunonCleared() {

super.onCleared()

jobs.forEach{it.cancel() }

   }

}

这里有一些协程代码,还有点不太懂。

3.4.1.2 BaseViewModel

openclassBaseViewModel(

schedulerProvider:SchedulerProvider

) :RxViewModel(schedulerProvider) {

valprogress=ObservableField(false)

valisRefreshing=ObservableField(false)

valisError=ObservableField(false)

valerrMsg=ObservableField("")

}

添加一些状态代码如加载,进度条,错误以及错误信息。都通过ObservableField来定义,具体使用可以通过xml查看,以后还要再细品。

3.4.2 adapter

3.4.2.1 BaseBindableAdapter

interfaceBaseBindableAdapter{

funsetHeader(items:T) {}

funsetData(items:List) {}

funsetFooter(items:T) {}

funbind(data:T) {}

}

3.4.2.2 GenericAdapter

abstractclassGenericAdapter:

RecyclerView.Adapter,

BaseBindableAdapter{

varlistItems:List

constructor(listItems:List) {

this.listItems=listItems

notifyDataSetChanged()

   }

constructor() {

listItems=emptyList()

notifyDataSetChanged()

   }

overridefunsetData(items:List) {

this.listItems=items

notifyDataSetChanged()

   }

// TODO: To add header?

overridefunsetHeader(items:DATA) {

(this.listItemsasMutableList).add(items)

notifyItemInserted(0)

   }

// TODO: To add footer?

overridefunsetFooter(items:DATA) {

(this.listItemsasMutableList).add(items)

notifyItemInserted(this.listItems.size-1)

   }

overridefunonCreateViewHolder(parent:ViewGroup,viewType:Int):RecyclerView.ViewHolder{

returngetViewHolder(

DataBindingUtil.inflate(LayoutInflater.from(parent.context)

,viewType

,parent

,false)!!)

   }

@SuppressWarnings("Unchecked cast")

overridefunonBindViewHolder(holder:RecyclerView.ViewHolder,position:Int) {

(holderas?BaseBindableAdapter)?.bind(listItems[position])

   }

overridefungetItemCount():Int{

returnlistItems.size

   }

overridefungetItemViewType(position:Int):Int{

returngetLayoutId(position,listItems[position])

   }

protectedabstractfungetLayoutId(position:Int,obj:DATA):Int

// TODO: Use generic ViewDataBinding

abstractfungetViewHolder(viewBinding:ViewDataBinding):RecyclerView.ViewHolder

}

3.4.3 数据绑定相关

CardViewBinding、ListBiding、ProgressBinding以及ViewBiding

同时添加了几个Ext类

objectListBinding{

@SuppressLint(value=["PrivateResource","UNCHECKED_CAST"])

@BindingAdapter(value=["list:isGrid",

"list:spanCount",

"list:orientation",

"list:isReversed"],requireAll=false)

@JvmStatic

// TODO: Receive generic ViewDataBinding as args

funRecyclerView.initAdapter(isGrid:Boolean=false,

spanCount:Int=0,

orientation:Int=0,

isReversed:Boolean=false) {

try{

if(isGrid)setupGridLayoutManager(spanCount,orientation,isReversed)

elsesetupLinearLayoutManager(orientation,isReversed)

}catch(e:Exception) {

e.printStackTrace()

       }

   }

@SuppressLint(value=["PrivateResource","UNCHECKED_CAST"])

@BindingAdapter(value=["list:layoutId","list:viewType"],requireAll=false)

@JvmStatic

funRecyclerView.initViewHolder(layoutId:Int,

viewType:Int?) {

try{

adapter=object:GenericAdapter() {

overridefungetLayoutId(position:Int,obj:DATA):Int{

returnlayoutId

               }

// TODO: Refactor to generic instead of using when condition

overridefungetViewHolder(viewBinding:ViewDataBinding):RecyclerView.ViewHolder{

returnPostViewHolder(viewBindingasPostItemBinding)

               }

           }

}catch(e:Exception) {

e.printStackTrace()

       }

   }

@SuppressLint(value=["PrivateResource","UNCHECKED_CAST"])

@BindingAdapter(value=["list:items"],requireAll=false)

@JvmStatic

funRecyclerView.initData(items:List?) {

try{

if(adapterisGenericAdapter<*>) {

(adapterasGenericAdapter).setData(items?:emptyList())

           }

}catch(e:Exception) {

e.printStackTrace()

       }

   }

@SuppressLint(value=["PrivateResource","UNCHECKED_CAST"])

@BindingAdapter(value=["list:items"],requireAll=false)

@JvmStatic

funRecyclerView.initData(items:Set?) {

try{

if(adapterisGenericAdapter<*>) {

(adapterasGenericAdapter).setData(items?.toList()?:emptyList())

           }

}catch(e:Exception) {

e.printStackTrace()

       }

   }

将数据显示和Databinding代码混合在一起,可以考虑分开。因为这样我就必须要开始界面PostViewHolder、以及PostItemBinding的编写(暂时放在后面)。

3.4.4 liveData相关

LiveEvent以及SingleLiveEvent

其他没有用到的暂时不理会,需要时再迁移过来。

3.5 界面部分

3.5.1 基础类

BaseActivity

openclassBaseActivity:AppCompatActivity(),ToolbarListener{

overridefunonSupportNavigateUp():Boolean{

finish()

returntrue

   }

overridefunonBackPressed() {

super.onBackPressed()

overridePendingTransition(R.anim.slide_up,R.anim.slide_down)

   }

overridefunsetupToolbar(toolbar:Toolbar) {

setSupportActionBar(toolbar)

   }

overridefunupdateTitleToolbar(newTitle:String) {

supportActionBar?.apply{

setDisplayHomeAsUpEnabled(true)

title=newTitle

subtitle=""

       }

   }

}

BaseActivity只是增加了一些动画和Toolbar标题,不过这个在我的这个版本里面没有调通。

BaseUserActionListener

interfaceBaseUserActionListener{

funonRefresh()

}

用于加载页面的接口。

3.5.2 MainActivity

MainActivity使用的是navigation组件中的Fragment跳转管理(不知对不对,我暂时也只是调通了,没有仔细研究)。

classMainActivity:BaseActivity() {

privatevalviewBinding:ActivityMainBindingbylazy{

DataBindingUtil.setContentView(this,R.layout.activity_main)

   }

privatelateinitvarmNavHost:NavHostFragment

privatelateinitvarmNavController:NavController

privatelateinitvarappBarConfiguration:AppBarConfiguration

overridefunonCreate(savedInstanceState:Bundle?) {

super.onCreate(savedInstanceState)

//初始化数据绑定

viewBinding.executePendingBindings()

setupNavController()

setupAppBar()

if(::mNavController.isInitialized&&::appBarConfiguration.isInitialized) {

setupActionBar(mNavController,appBarConfiguration)

       }

updateTitleToolbar("kivy")

   }

overridefunonBackPressed() {

if(::mNavHost.isInitialized) {

valfragmentsSize=mNavHost.childFragmentManager.fragments.size

if(fragmentsSize>=1) {

super.onBackPressed()

}else{

findNavController(R.id.navHostFragment).navigateUp(appBarConfiguration)

           }

       }

   }

overridefunonSupportNavigateUp():Boolean{

returnif(::mNavHost.isInitialized) {

findNavController(R.id.navHostFragment).navigateUp(appBarConfiguration)

}else{

false

       }

   }

privatefunsetupNavController() {

mNavHost=supportFragmentManager

.findFragmentById(R.id.navHostFragment)asNavHostFragment??:return

mNavController=mNavHost.navController

   }

privatefunsetupAppBar() {

appBarConfiguration=AppBarConfiguration(

setOf(R.id.postFragment),

null

       )

   }

privatefunsetupActionBar(

navController:NavController,

appBarConfiguration:AppBarConfiguration

   ) {

setupToolbar(viewBinding.toolbar.toolbar)

setupActionBarWithNavController(navController,appBarConfiguration)

   }

}

同时要将布局文件以及navigation的跳转xml迁移过来。样式,颜色,名称都不是重点可以直接复制过来。

xmlns:app="http://schemas.android.com/apk/res-auto"

>

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/toolbar"

layout="@layout/layout_toolbar"/>

android:id="@+id/navHostFragment"

android:name="androidx.navigation.fragment.NavHostFragment"

android:layout_width="match_parent"

android:layout_height="match_parent"

app:defaultNavHost="true"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintRight_toRightOf="parent"

app:layout_constraintTop_toBottomOf="@id/toolbar"

app:navGraph="@navigation/home_nav_graph"/>

fragment指到navigation中的NavHostFragment,具体的操作在home_nav_graph中。

由于实际操作和我分析代码是不一样的,所以这里要将一些Fragment,layout文件先添加上来。

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/homeNavGraph"

app:startDestination="@id/postFragment"

>

android:id="@+id/postFragment"

android:name="xyz.wayhua.kivy101.ui.main.fragment.PostFragment"

android:label="PostFragment"

tools:layout="@layout/post_fragment">

android:id="@+id/toPostDetailAction"

app:destination="@id/postDetailFragment">

android:name="postItem"

app:argType="xyz.wayhua.kivy.ui.main.fragment.PostItem"/>

android:id="@+id/postDetailFragment"

android:name="xyz.wayhua.kivy101.ui.main.fragment.PostDetailFragment"

android:label="PostDetailFragment"

tools:layout="@layout/postdetail_fragment">

android:name="postItem"

app:argType="xyz.wayhua.kivy101.ui.main.fragment.PostItem"

app:nullable="true"

/>

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

funRecyclerView.initViewHolder(

layoutId:Int,

viewType:Int?

)=try{

adapter=object:GenericAdapter() {

overridefungetLayoutId(position:Int,obj:DATA):Int{

returnlayoutId

           }

// TODO: Refactor to generic instead of using when condition

overridefungetViewHolder(viewBinding:ViewDataBinding):RecyclerView.ViewHolder{

returnPostViewHolder(viewBindingasPostItemBinding)

           }

       }

}catch(e:Exception) {

e.printStackTrace()

   }

3.5.2.2 PostViewHolder

classPostViewHolder(valbinding:PostItemBinding) :

RecyclerView.ViewHolder(binding.root),

BaseBindableAdapter{

overridefunbind(data:PostItem) {

binding.apply{

item=data

root.setOnClickListener{

//                val toPostDetail = PostFragmentDirections.toPostDetailAction(

//                    data

//                )

//

//                it.findNavController().navigate(toPostDetail)

           }

executePendingBindings()

       }

   }

}

由于使用了数据绑定,就比较简单了,这里添加的单击事件不在此次考虑中,所以注释了。

3.5.2.3 ViewModel

classPostViewModel(

privatevalappRepository:AppRepository,

schedulerProvider:SchedulerProvider

) :BaseViewModel(schedulerProvider){

valkeywords=Channel(Channel.UNLIMITED)

varpostListSet=MutableSetObservableField()

/*

* We use LiveEvent to publish "states"

* No need to publish and retain any view state

*/

privateval_states=LiveEvent()

valstates:LiveData

get()=_states.toSingleEvent()

fungetPosts() {

_states.value=LoadingState

launch{

try{

valposts=appRepository.getPostsAsync().await()

_states.value=PostListState.from(posts!!)

}catch(error:Throwable) {

_states.value=ErrorState(error)

           }

       }

   }

funsearchPosts(query:String) {

if(query.isNotBlank()) {

_states.value=LoadingState

launch{

try{

valposts=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

*/

sealedclassState

/**

* Generic Loading State

*/

objectLoadingState:State()

/**

* Generic Error state

* @param error - caught error

*/

dataclassErrorState(valerror:Throwable) :State()

dataclassPostListState(

vallist:List

) :State() {

companionobject{

funfrom(list:List):PostListState{

returnwith(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

创建布局

overridefunonCreateView(

inflater:LayoutInflater,

container:ViewGroup?,

savedInstanceState:Bundle?

):View?{

viewBinding=PostFragmentBinding.inflate(inflater,container,false)

.apply{

listener=this@PostFragment

vm=viewModel

           }

returnviewBinding.root

   }

拉取数据

privatefunpullToRefresh() {

viewModel.apply{

isRefreshing.set(true)

if(::onLoadMore.isInitialized)onLoadMore.resetState()

getPosts()

       }

   }

根据状态处理数据

privatefunsetupObserver()=with(viewModel) {

// Observe ComposeState

states.observe(viewLifecycleOwner,Observer{state->

state?.let{

when(state) {

isLoadingState->showIsLoading()

isPostListState->{

showCategoryItemList(

posts=state.list.map{PostItem.from(it) })

                   }

isErrorState->showError(state.error)

               }

           }

       })

coroutineLaunch(Main) {

keywords.consumeEach{searchPosts(it) }

       }

   }

当state为PostListState时,有一个转换过程,将域数据Post转为了PostItem。

其他代码基本上是对以上代码的补充。排除编译错误后运行。

运行代码,结果报错:

Causedby:org.koin.core.error.NoBeanDefFoundException:Nodefinitionfoundforclass:'xyz.wayhua.kivy101.ui.main.fragment.PostViewModel'.Checkyourdefinitions!

atorg.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:247)

其原因是引用viewmodel是通过by viewModel()来实现的,要通过koin注入进来

privatevalviewModelbyviewModel()

要将viewmodel配置到module中。

3.5.4 di 补充

valviewModelModule=module{

viewModel{PostViewModel(get(),get()) }

}

valallModules=listOf(rxModule,roomModule,viewModelModule,remoteDatasourceModule,repoModule)

构造函数中的get(),get(),其实是告诉我们这里有两个参数,都必须在module中配置。

4 总结

总体来说,这一次将代码进行了一次清理,同时结构更加清晰。仍然有很多问题,前面就说过如ListBinding直接指定Adapter,虽然少了一个类,但是确实也不太方便,现在只有一个页面,如果有非常多的页面是否要同时修改那个地方,如果有多种viewholder要显示呢?

还有就是结合以前编写程序的习惯,我更倾向于职责分离。这个会有深挖该源码以后,进一步深入。还有一个问题是中国人的习惯,上拉刷新,下拉加载更多,怎么处理。这里的页面是一次性加载100条,分页怎么办?

还有数据只是个补充,主要用途可能就是fts,没有起到缓存数据的作用。

5 重构

5.1 seal State类问题

在Status.kt文件中引用了Post类,如下

dataclassPostListState(

vallist:List

) :State() {

companionobject{

funfrom(list:List):PostListState{

returnwith(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,这开始只是一个设想。

dataclassSuccessState(valdata:List) :State() {

companionobject{

funfrom(data:List):SuccessState{

returnwith(data) {

when{

isEmpty()->error("不能为空")

else->SuccessState(this)

               }

           }

       }

   }

}

如上编写,结果编译通过了。于是我就有一个大胆的想法,替换掉PostListState。

由于前面代码本来就是可以运行的。重构最好做到一次重构一个地方,现在是替换掉PostListState,那最好的办法就是直接注释掉PostListState类,然后编译,将所有的报错一一改掉,再看运行效果,如果没问题,那就表示重构成功了。

PostFragment中要替换后的代码

privatefunsetupObserver()=with(viewModel) {

// Observe ComposeState

states.observe(viewLifecycleOwner,Observer{state->

state?.let{

when(state) {

isLoadingState->showIsLoading()

isSuccessState<*>->{

showCategoryItemList(

posts=state.data.map{PostItem.from(itasPost) })

                   }

isErrorState->showError(state.error)

               }

           }

       })

coroutineLaunch(Main) {

keywords.consumeEach{searchPosts(it) }

       }

   }

SuccessState<*> 表示,里面代码要强制转换。

PostViewModel中要同样替换为

fungetPosts() {

_states.value=LoadingState

launch{

try{

valposts=appRepository.getPostsAsync().await()

_states.value=SuccessState.from(posts!!)

}catch(error:Throwable) {

_states.value=ErrorState(error)

           }

       }

   }

funsearchPosts(query:String) {

if(query.isNotBlank()) {

_states.value=LoadingState

launch{

try{

valposts=appRepository.searchPostsAsync(query).await()

_states.value=SuccessState.from(posts!!)

}catch(error:Throwable) {

_states.value=ErrorState(error)

               }

           }

       }

   }

再次运行,发现结果和原来一模一样。

你可能感兴趣的:(Kotlin学习之开源代码分析、重构(二))