Android Airbnb Mavericks 架构教程

引言

在现代Android开发中,选择合适的架构对提高应用的可维护性和扩展性至关重要。近年来,越来越多的开发者开始关注如何更好地管理应用的状态,确保应用在各种情况下都能正确地响应用户的操作和系统的变化。Mavericks是由Airbnb开发的一个轻量级但功能强大的状态管理框架,它旨在简化这一过程。

Mavericks是一个基于Kotlin的Android状态管理库,提供了一种简洁、高效的方式来管理应用的状态。它采用了MVI(Model-View-Intent)架构模式,这是现代Android开发中一种非常流行的架构模式。

MVI架构简介

MVI(Model-View-Intent)是一种单向数据流模式,它通过明确的状态管理和事件处理,使应用的逻辑更加清晰和可预测。MVI架构由以下三个核心部分组成:

  • Model: 代表应用的状态。它是一个不可变的数据类,定义了界面当前所处的状态。
  • View: 负责渲染UI,并将用户的输入转化为Intent。
  • Intent: 用户的操作或系统的事件,这些事件会触发状态的改变。

Mavericks中的MVI

在Mavericks中,MVI架构被具体化为以下组件:

  • State: 表示UI的状态,通常是一个数据类,实现MavericksState接口。
  • ViewModel: 负责管理状态和处理业务逻辑,通过调用setState方法来更新状态。
  • Fragment/Activity: 作为View,订阅ViewModel中的状态变化并更新UI。

为什么选择Mavericks?

在传统的Android开发中,管理复杂的UI状态和处理状态变化是一项具有挑战性的任务。特别是在涉及到多个视图和复杂的用户交互时,保持状态的一致性和正确性变得尤为困难。Mavericks通过提供结构化的状态管理方案,帮助开发者更好地应对这些挑战。

  • 单一来源的真理:所有的UI状态都存储在单一的ViewModel中,确保状态的一致性和可追溯性。
  • 简洁的状态定义:状态通常是一个数据类,易于定义和理解。
  • 自动订阅和更新UI:通过观察ViewModel中的状态变化,UI能够自动更新,无需手动处理复杂的订阅逻辑。
  • Kotlin的强大功能:利用Kotlin的协程和扩展函数,Mavericks能够提供简洁而强大的API,使开发过程更加顺畅。

总的来说,Mavericks是一个功能强大且易于使用的MVI框架,适用于各种规模的Android应用开发。不论是初学者还是有经验的开发者,都能从中受益。

目录

  1. 什么是Mavericks?
  2. 环境配置
  3. 创建项目
  4. 架构概述
  5. 编写第一个Mavericks视图模型
  6. 高级功能
    1. 状态恢复和持久化
    2. 与协程的集成
    3. 状态管理
    4. 网络请求和数据加载
  1. 总结

1. 什么是Mavericks?

Mavericks是由Airbnb开发的一个Android状态管理库。它以其简洁的API和强大的功能被广泛应用于现代Android开发中。Mavericks结合了Kotlin协程,使状态管理和UI更新变得更加直观和高效。

1. 环境配置

arduino 复制代码
dependencies {
    implementation 'com.airbnb.android:mavericks:x.y.z'
}

3. 创建项目

在Android Studio中创建一个新的项目,选择空活动模板。项目创建完成后,按照上面的步骤配置好Gradle文件。

4. 架构概述

Mavericks架构主要由三个部分组成:State、ViewModel 和 Fragment/Activity。

  • State: 定义界面的状态,通常是一个数据类。
  • ViewModel: 负责业务逻辑和状态管理。
  • Fragment/Activity: 用于显示UI并订阅ViewModel的状态。

5. 编写第一个Mavericks视图模型

在本节中,我们将展示如何使用Mavericks编写一个视图模型,并与传统的写法进行对比。通过这种对比,你可以更好地理解Mavericks的优势。

需求:设置一个 count 通过加和减后显示在 TextView 上。

传统写法

直接声明变量

在传统的Android开发中,我们可能会直接在Fragment中声明一个变量,并通过按钮点击事件直接修改这个变量:

kotlin 复制代码
class CounterFragment : Fragment(R.layout.fragment_counter) {

    private var count = 0

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

        val tvCount = view.findViewById<TextView>(R.id.tvCount)
        val btnIncrement = view.findViewById<Button>(R.id.btnIncrement)
        val btnDecrement = view.findViewById<Button>(R.id.btnDecrement)

        tvCount.text = "Count: $count"

        btnIncrement.setOnClickListener {
            count++
            tvCount.text = "Count: $count"
        }

        btnDecrement.setOnClickListener {
            count--
            tvCount.text = "Count: $count"
        }
    }
}

这种方法虽然简单,但缺乏结构,容易导致状态管理混乱,特别是在处理复杂逻辑时。

使用LiveData和ViewModel

另一种常见的做法是使用Android Architecture Components中的LiveData和ViewModel:

kotlin 复制代码
class CounterViewModel : ViewModel() {
    private val _count = MutableLiveData(0)
    val count: LiveData<Int> get() = _count

    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }

    fun decrement() {
        _count.value = (_count.value ?: 0) - 1
    }
}

class CounterFragment : Fragment(R.layout.fragment_counter) {

    private lateinit var viewModel: CounterViewModel

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

        viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)

        val tvCount = view.findViewById<TextView>(R.id.tvCount)
        val btnIncrement = view.findViewById<Button>(R.id.btnIncrement)
        val btnDecrement = view.findViewById<Button>(R.id.btnDecrement)

        viewModel.count.observe(viewLifecycleOwner, Observer { count ->
            tvCount.text = "Count: $count"
        })

        btnIncrement.setOnClickListener {
            viewModel.increment()
        }

        btnDecrement.setOnClickListener {
            viewModel.decrement()
        }
    }
}

这种方法比直接声明变量更好,因为它将状态和业务逻辑分离到了ViewModel中,并且使用LiveData来观察状态变化,确保UI能够自动更新。然而,LiveData的使用仍然需要一定的样板代码,并且在处理复杂状态和异步操作时,代码可能会变得繁琐。在接下来的部分中,我们将继续展示如何使用Mavericks创建UI并绑定视图模型。

使用Mavericks编写视图模型

首先,我们创建一个数据类来表示界面的状态:

kotlin 复制代码
// 注意:MavericksState
data class CounterState(val count: Int = 0) : MavericksState

接下来,创建一个MavericksViewModel来管理这个状态:

kotlin 复制代码
// 注意 MavericksViewModel
class CounterViewModel(initialState: CounterState) : MavericksViewModel<CounterState>(initialState) {
    // 设置新的 state
    fun increment() = setState { copy(count = count + 1) }
    fun decrement() = setState { copy(count = count - 1) }
}

在Mavericks中,状态是不可变的,每次状态变化都会创建一个新的状态对象。这使得状态管理更加安全和可预测。

使用Mavericks的Fragment

然后,在Fragment中绑定ViewModel并更新UI:

kotlin 复制代码
// 注意 MavericksView
class CounterFragment : Fragment(R.layout.fragment_counter), MavericksView {

    private val viewModel: CounterViewModel by fragmentViewModel()

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

        val btnIncrement = view.findViewById<Button>(R.id.btnIncrement)
        val btnDecrement = view.findViewById<Button>(R.id.btnDecrement)

        btnIncrement.setOnClickListener {
            viewModel.increment()
        }

        btnDecrement.setOnClickListener {
            viewModel.decrement()
        }
    }

    // 当 state 发生改变时该回调会触发。
    override fun invalidate() {
        withState(viewModel) { state ->
            view?.findViewById<TextView>(R.id.tvCount)?.text = "Count: ${state.count}"
        }
    }
}

Mavericks的优势

与传统的写法相比,Mavericks在以下方面具有明显优势:

  1. 简洁的状态管理:状态管理在Mavericks中通过不可变的数据类和简洁的setState方法实现,减少了样板代码。
  2. 自动化的UI更新:Mavericks自动处理状态变化和UI更新,使代码更加简洁和易于维护。
  3. 更好的测试性:Mavericks的状态和业务逻辑集中在ViewModel中,便于进行单元测试和功能测试。
  4. 协程支持:Mavericks原生支持Kotlin协程,简化了异步操作的处理。

通过上面的对比,可以看出Mavericks在简化代码和提高开发效率方面具有显著优势。

6. 高级功能

状态恢复和持久化

在开发复杂的Android应用时,处理状态恢复和持久化是一个常见的需求。Mavericks提供了简洁而强大的工具来处理这些任务。

为什么需要状态恢复和持久化?

在Android应用中,状态恢复和持久化可以在以下场景中发挥重要作用:

  1. 配置更改:当设备旋转或语言改变时,Activity和Fragment会被销毁并重建。需要恢复先前的状态以保持用户体验一致。
  2. 应用进程重启:在内存不足时,系统可能会杀掉后台进程。当用户返回应用时,需要恢复之前的状态。
  3. 用户导航:当用户在多个页面之间导航时,保持页面状态可以提高用户体验。

使用Mavericks进行状态恢复

Mavericks利用ViewModel来管理状态,天然支持配置更改。由于ViewModel的生命周期与Activity或Fragment不同步,配置更改时,ViewModel不会被销毁,状态可以自动恢复。

使用Mavericks进行持久化

先看传统写法进行状态持久化

在传统的Android开发中,状态持久化通常通过 savedInstanceState 或 SharedPreferences 来实现。

使用 savedInstanceState:

kotlin 复制代码
class CounterFragment : Fragment(R.layout.fragment_counter) {

    private var count = 0

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

        val tvCount = view.findViewById<TextView>(R.id.tvCount)
        val btnIncrement = view.findViewById<Button>(R.id.btnIncrement)
        val btnDecrement = view.findViewById<Button>(R.id.btnDecrement)

        savedInstanceState?.let {
            count = it.getInt("COUNT", 0)
        }

        tvCount.text = "Count: $count"

        btnIncrement.setOnClickListener {
            count++
            tvCount.text = "Count: $count"
        }

        btnDecrement.setOnClickListener {
            count--
            tvCount.text = "Count: $count"
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt("COUNT", count)
    }
}

使用 SharedPreferences:

kotlin 复制代码
class CounterFragment : Fragment(R.layout.fragment_counter) {

    private var count = 0
    private lateinit var sharedPreferences: SharedPreferences

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

        sharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE)
        count = sharedPreferences.getInt("COUNT", 0)

        val tvCount = view.findViewById<TextView>(R.id.tvCount)
        val btnIncrement = view.findViewById<Button>(R.id.btnIncrement)
        val btnDecrement = view.findViewById<Button>(R.id.btnDecrement)

        tvCount.text = "Count: $count"

        btnIncrement.setOnClickListener {
            count++
            tvCount.text = "Count: $count"
            sharedPreferences.edit().putInt("COUNT", count).apply()
        }

        btnDecrement.setOnClickListener {
            count--
            tvCount.text = "Count: $count"
            sharedPreferences.edit().putInt("COUNT", count).apply()
        }
    }
}

这种方法虽然有效,但需要手动处理状态的存储和恢复,增加了样板代码的复杂性。

使用Mavericks进行状态持久化

Mavericks提供了@PersistState注解,可以更简洁地实现状态的持久化。以下是使用Mavericks进行状态持久化的示例:

在ViewModel中使用 @PersistState 注解:

kotlin 复制代码
data class CounterState(@PersistState val count: Int = 0) : MavericksState

class CounterViewModel(initialState: CounterState) : MavericksViewModel<CounterState>(initialState) {
    fun increment() = setState { copy(count = count + 1) }
    fun decrement() = setState { copy(count = count - 1) }
}

Mavericks会自动将标注了@PersistState的状态字段保存到内部持久化存储中,并在应用重启时自动恢复。这大大简化了状态管理的复杂性。

通过Mavericks的持久化特性,可以大幅减少手动管理状态存储和恢复的样板代码,使应用的状态管理更加简洁和高效。

与协程的集成

Mavericks的内部源码使用协程,无需集成。

状态管理

在开发复杂的Android应用时,管理应用的状态可能变得非常复杂。Mavericks提供了一套强大的工具来帮助开发者简化复杂状态的管理,使代码更加简洁和可维护。以下将介绍如何使用Mavericks处理复杂状态,包括多状态组合、依赖状态的管理以及网络请求和数据加载。

多状态组合

在实际应用中,一个界面可能涉及多个状态。例如,一个购物车界面需要显示商品列表、总价格和用户信息。在Mavericks中,可以通过定义多个状态类并在ViewModel中组合使用这些状态。

示例:购物车界面

kotlin 复制代码
data class Product(val id: Int, val name: String, val price: Double)
data class User(val id: Int, val name: String)

data class CartState(
    val products: List<Product> = emptyList(),
    val totalPrice: Double = 0.0,
    val user: User? = null
) : MavericksState

class CartViewModel(initialState: CartState) : MavericksViewModel<CartState>(initialState) {

    fun addProduct(product: Product) {
        setState {
            val newProducts = products + product
            copy(products = newProducts, totalPrice = newProducts.sumOf { it.price })
        }
    }

    fun removeProduct(product: Product) {
        setState {
            val newProducts = products - product
            copy(products = newProducts, totalPrice = newProducts.sumOf { it.price })
        }
    }

    fun setUser(user: User) {
        setState {
            copy(user = user)
        }
    }
}

依赖状态的管理

有时,一个状态的变化可能依赖于另一个状态。在Mavericks中,可以使用SharedViewModel模式来订阅其他状态的变化,并在状态变化时触发相应的操作。

示例:使用SharedViewModel管理用户和购物车的依赖关系

首先,创建一个UserViewModel来管理用户状态:

kotlin 复制代码
data class UserState(val user: User? = null) : MavericksState

class UserViewModel(initialState: UserState) : MavericksViewModel<UserState>(initialState) {

    fun setUser(user: User) {
        setState {
            copy(user = user)
        }
    }
}

然后,在Fragment中共享这个UserViewModel并将用户状态传递给CartViewModel:

kotlin 复制代码
class CartFragment : Fragment(R.layout.fragment_cart), MavericksView {

    private val userViewModel: UserViewModel by activityViewModel()
    private val cartViewModel: CartViewModel by fragmentViewModel()

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

        // 对于非Async对象的State监听可以使用 onEach
        userViewModel.onEach(UserState::user) { user ->
            cartViewModel.setUser(user)
        }

        // 继续设置购物车的UI逻辑
    }

    override fun invalidate() {
        // 更新UI逻辑
    }
}

在这个示例中,我们使用activityViewModel来共享UserViewModel,并使用onEach来监听用户状态的变化。然后,将用户状态传递给CartViewModel。

使用onEach监听状态变化

onEach方法用于监听状态的变化,并在变化时触发相应的操作。可以用于处理UI更新或触发其他业务逻辑。

示例:监听购物车中的商品数量变化

kotlin 复制代码
data class CartState(
    val products: List<Product> = emptyList(),
    val totalPrice: Double = 0.0,
    val user: User? = null
) : MavericksState

class CartViewModel(initialState: CartState) : MavericksViewModel<CartState>(initialState) {

    init {
        onEach(CartState::products) { products ->
            println("Number of products in cart: ${products.size}")
        }
    }

    fun addProduct(product: Product) {
        setState {
            val newProducts = products + product
            copy(products = newProducts, totalPrice = newProducts.sumOf { it.price })
        }
    }

    fun removeProduct(product: Product) {
        setState {
            val newProducts = products - product
            copy(products = newProducts, totalPrice = newProducts.sumOf { it.price })
        }
    }
}

在这个示例中,CartViewModel使用onEach监听products列表的变化,并在每次变化时输出当前购物车中商品的数量。

使用派生属性

派生属性用于从现有状态派生出新的值。派生属性通常是基于当前状态计算得出的,并可以通过onEach方法监听其变化。

示例:计算购物车中商品的总数量

kotlin 复制代码
data class CartState(
    val products: List<Product> = emptyList(),
    val totalPrice: Double = 0.0,
    val user: User? = null
) : MavericksState {
    val totalItems: Int get() = products.size
}

class CartViewModel(initialState: CartState) : MavericksViewModel<CartState>(initialState) {

    init {
        onEach(CartState::totalItems) { totalItems ->
            println("Total items in cart: $totalItems")
        }
    }

    fun addProduct(product: Product) {
        setState {
            val newProducts = products + product
            copy(products = newProducts, totalPrice = newProducts.sumOf { it.price })
        }
    }

    fun removeProduct(product: Product) {
        setState {
            val newProducts = products - product
            copy(products = newProducts, totalPrice = newProducts.sumOf { it.price })
        }
    }
}

在这个示例中,CartState定义了一个派生属性totalItems,用于计算购物车中的商品总数量。CartViewModel使用onEach监听totalItems的变化,并在每次变化时输出当前购物车中的总商品数量。

通过onEach和派生属性,Mavericks能够更高效地管理复杂的状态变化,使得代码更加简洁和可维护。

网络请求和数据加载

处理网络请求和数据加载是现代应用开发的常见需求。Mavericks通过与Kotlin协程的集成,使得异步操作的管理变得非常简洁。可以使用execute扩展函数来管理网络请求,并确保在状态变化时更新UI。

Mavericks提供了一个特殊的Async对象来处理异步状态。Async对象可以表示一个加载中的状态、一个成功的状态或一个失败的状态。通过使用Async对象,可以更简洁地管理异步操作的结果。

示例:异步加载用户数据并管理其状态

kotlin 复制代码
data class UserState(val user: Async<User> = Uninitialized) : MavericksState

class UserViewModel(initialState: UserState) : MavericksViewModel<UserState>(initialState) {

    private val apiService: ApiService = // 初始化你的ApiService

    fun fetchUser(id: Int) {
        suspend {
            apiService.getUser(id)
        }.execute { result ->
            copy(user = result)
        }
    }
}

在上述示例中,execute扩展函数会自动处理协程的执行,并将结果包装成Async对象。这样可以轻松管理加载、成功和失败的状态。

在传统的View中,可以通过以下方式显示加载中、成功和失败的状态:

kotlin 复制代码
class UserFragment : Fragment(R.layout.fragment_user), MavericksView {

    private val viewModel: UserViewModel by fragmentViewModel()

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

        viewModel.fetchUser(1)

        // 观察状态并更新UI
        viewModel.onAsync(UserState::user) { asyncUser ->
            when (asyncUser) {
                is Uninitialized -> {
                    // 初始状态
                    view.findViewById<TextView>(R.id.userNameTextView).text = "No user"
                }
                is Loading -> {
                    // 显示加载中
                    view.findViewById<ProgressBar>(R.id.progressBar).isVisible = true
                }
                is Success -> {
                    // 显示成功状态
                    view.findViewById<ProgressBar>(R.id.progressBar).isVisible = false
                    view.findViewById<TextView>(R.id.userNameTextView).text = asyncUser()?.name
                }
                is Fail -> {
                    // 显示失败状态
                    view.findViewById<ProgressBar>(R.id.progressBar).isVisible = false
                    view.findViewById<TextView>(R.id.errorTextView).apply {
                        isVisible = true
                        text = asyncUser.error.message
                    }
                }
            }
        }
    }

    override fun invalidate() {
        // 在这里可以放置需要更新UI的其他逻辑
    }
}

题外话:发挥下想象,如果使用 Jetpack Compose 会是什么效果?

通过以上这些方法,Mavericks可以帮助开发者更高效地管理复杂的应用状态,使代码更加简洁和可维护。

7. 总结

通过本教程,你已经了解了如何使用Mavericks构建一个简单的Android应用。Mavericks的强大之处在于其简洁的API和强大的状态管理能力,使得构建复杂应用变得更加轻松。希望你能通过本教程掌握Mavericks,并将其应用到你的项目中。

参考:Maverick 官网GithubWiki

相关推荐
非正式程序猿42 分钟前
Android 权限申请分享
android
小菜琳2 小时前
Android系统adb shell dumpsys activity processes
android
ChatMoneyAI2 小时前
php简单的单例模式
android·php
小菜琳2 小时前
单例模式在 Android中的应用
android
へんしんへんしん8 小时前
MySQL主从复制与读写分离
android·github
mrathena11 小时前
Windows 11 安装 安卓子系统 (WSA)
android·windows
DaSunWarman13 小时前
自动翻译 android/res/values/strings.xml
android·xml·机器翻译·strings.xml
LuckyRich114 小时前
【MySQL】mysql访问
android·数据库·mysql·adb
江畔何年初見月14 小时前
餐饮点餐系统SQL
android·sql·信息可视化
nbplus_00717 小时前
Swagger php注解常用语法梳理
android·开发语言·php·个人开发·laravel·swagger