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架构的未来舞台。

相关推荐
Myli_ing15 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风17 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
软件小伟26 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒2 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript