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) {
        [email protected] {
            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架构的未来舞台。

相关推荐
zhougl99630 分钟前
html处理Base文件流
linux·前端·html
花花鱼34 分钟前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_37 分钟前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo2 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端4 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡4 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木5 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!5 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
難釋懷6 小时前
JavaScript基础-移动端常见特效
开发语言·前端·javascript