Android的MVI架构最佳实践(二)

Android的MVI架构最佳实践(二):View和repeatOnLifecycle

预告:

前言

我们已经搭建了MVI中的M和I,对于View封装需要定义BaseActivity或者BaseFragment,很多同学并不喜欢使用BaseX的封装。因此在这个实践指导中这个环节并不是很重要,你可以随性直接看下一个小节。

BaseFragment

在现在构建一个android应用都是Single Activity的,所以我们也只封装下Fragment来消除一些模版代码。由于是单线数据流不需要DataBinding, layout我们首选ViewBinding

kotlin 复制代码
abstract class BaseFragment<VB : ViewBinding, VM : BaseViewModel<*, *, *>>(
    val viewBinding: (LayoutInflater, ViewGroup?, Boolean) -> VB
) : Fragment() {

    protected abstract val viewModel: VM

    private var _binding: VB? = null

    protected val binding: VB
        get() = requireNotNull(_binding) { "The property of binding has been destroyed." }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        activity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN)
        binding = viewBinding(inflater, container, false)
        return binding?.run {
            initRenderers(this)
            root
        }
    }

    abstract fun initRenderers(binding: VB)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        observeStateOrEvent(viewModel)
    }

    abstract fun observeStateOrEvent(viewModel: VM)

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

创建一个Fragment看看

kotlin 复制代码
class EditFragment :BaseFragment<FragmentEditBinding, EditViewModel>(FragmentEditBinding::inflate) {

    override val viewModel by viewModels<EditViewModel>()

    override fun initRenderers(binding: FragmentEditBinding) {
        with(binding) {
            title.text = "aaa"
        }
    }

    override fun observeStateOrEvent(viewModel: EditViewModel) {
        viewLifecycleOwner.lifecycleScope.launchWhenResumed {
            viewModel.state.collect {
                // binding.title.text = it.content
            }
        }
    }
}

很快啊,我们发现一些问题,再一个一个解决:

  • BaseFragment已经在泛型中定义了FragmentEditBinding但是还是需要FragmentEditBinding调用方法才能真正注入binding对象,重复出现这不够优雅。
  • launchWhenResumed 被标记为过时方法,原来StateFlow是无生命周期感知的,也就是在应用后台时候Flow还是在collect数据,只要collect调用了当前的lifecycleScope就不会停止,除非到生命周期结束。

优雅的创建ViewBinding

所有ViewBinding生成的代码都会实现一个接口androidx.viewbinding.ViewBinding, 并且自动生成的代码中都会有inflate()重载静态方法,那我们可以通过反射获取并且调用方法。

java 复制代码
public interface ViewBinding {
    @NonNull
    View getRoot();
}
  1. Class.getGenericSuperclass() 转换为ParameterizedType

  2. 通过Type.getActualTypeArguments()获取到所有的泛型定义

  3. 然后if(ViewBinding::class.java.isAssignableFrom(result))找到是实现ViewBinding的类class

  4. 反射inflate()创建ViewBinding

    kotlin 复制代码
     internal fun <V : ViewBinding?> withGenericViewBinding(aClass: Class<*>): V {
         return try {
             val genericSuperclass = aClass.genericSuperclass
             if (genericSuperclass is ParameterizedType) {
                 genericSuperclass.actualTypeArguments.forEach {
                     val result: Class<*>
                     try {
                         result = it as Class<V>
                     } catch (_: Exception) {
                         return@forEach
                     }
                     if (ViewBinding::class.java.isAssignableFrom(result)) {
                         return result.getMethod(
                                 "inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java
                         ).invoke(null, inflater, container, false)
                     }
                 }
             }
             withGenericViewBinding(aClass.superclass, block)
         } catch (e: Exception) {
             e.printStackTrace()
             throw IllegalArgumentException("There is no generic of ViewBinding.")
         }
     }
  5. 配置consumerProguardFiles, 配置的文件会把aar的混淆规则最终应用到APP中,需要检查lib的build.gradle中如下:

    groovy 复制代码
    defaultConfig {
        ....
        consumerProguardFiles "consumer-rules.pro"
    }
  6. 添加混淆规则到consumer-rules.pro

    properties 复制代码
    # consumer-rules.pro 中混淆规则
    -keep public interface androidx.viewbinding.ViewBinding
    -keep class * implements androidx.viewbinding.ViewBinding {
        public static * inflate(android.view.LayoutInflater);
        public static * inflate(android.view.LayoutInflater, android.view.ViewGroup, boolean);
    }

去掉ViewBinding调用方法最终效果

kotlin 复制代码
class EditFragment :BaseFragment<FragmentEditBinding, EditViewModel>() {

    override val viewModel by viewModels<EditViewModel>()

    override fun initRenderers(binding: FragmentEditBinding) {
        with(binding) {
            title.text = "aaa"
        }
    }

    override fun observeStateOrEvent(viewModel: EditViewModel) {
        viewLifecycleOwner.lifecycleScope.launchWhenResumed {
            viewModel.state.collect {
                // binding.title.text = it.content
            }
        }
    }
}

repeatOnLifecycle

对比LiveData,Flow是无生命周期感知的,也就是在应用后台时候Flow还是在collect数据,只要collect调用了当前的lifecycleScope就不会停止,除非到生命周期结束。因此官方提供了一个repeatOnLifecycle在应用进入后台时候停止collect,当进入设置的生命周期会在此重新collect。使用此函数需要依赖 androidx.lifecycle:lifecycle-runtime-ktx:2.6.1, 同时这个包已经支持Flow的扩展函数

kotlin 复制代码
public fun <T> Flow<T>.flowWithLifecycle(
    lifecycle: Lifecycle,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED
): Flow<T> = callbackFlow {
    lifecycle.repeatOnLifecycle(minActiveState) {
        this@flowWithLifecycle.collect {
            send(it)
        }
    }
    close()
}

封装下fragment的flowWithLifecycle

kotlin 复制代码
fun <T> Flow<T>.observeWithLifecycle(
    fragment: androidx.fragment.app.Fragment,
    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
    collector: FlowCollector<T>
): Job = fragment.viewLifecycleOwner.lifecycleScope.launch {
    flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle, minActiveState).collect(collector)
}

//Sample
class SampleFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        flow.observeWithLifecycle(this){
            // Consume flow emissions
        }
    }
}

总结

此篇中我们对BaseFragment进行封装,并使用反射优雅的创建ViewBinding。在Flow的使用中,为了实现和LiveData一样的生命周期感知,了解了官方提供的核心方法repeatOnLifecycle。 下篇中我们会把MVI移植到compose中,并搭建一个快速手脚架,毕竟compose才是MVI架构的未来舞台。

相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写8 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
史努比.8 小时前
redis群集三种模式:主从复制、哨兵、集群
前端·bootstrap·html