零基础也能懂:用 MVVM 写出第一个像样的移动 App

CtrlC工程师
2025-12-22 12:42
阅读 743

大家好!我是你们的老朋友,一个在大厂写了三年代码、业余时间在 B站做技术分享的开发者。今天我想带大家走进移动应用架构的世界——特别是 MVVM(Model-View-ViewModel) 这个听起来高大上、但其实特别实用的设计模式。

你可能会问:“我又不是架构师,为什么要学这个?”
我当初学的时候也这么想。结果呢?写了个小项目,界面一改就崩,数据一多就乱,调 bug 调到凌晨三点。后来才明白:不是你会写代码就能做出好 App,而是你得知道怎么“组织”代码。

所以今天这篇教程,不讲抽象理论,只讲 问题 → 思路 → 代码 的实战路径。哪怕你是第一次听说“后端”和“前端”的区别,也能跟着一步步做出一个结构清晰、易于维护的小应用。


为什么我们需要 MVVM?

想象你在搭积木:

  • 如果所有积木都堆在一起,想换一块红色的?得把整座塔拆了。
  • 但如果红色积木单独放在一个盒子里,蓝色在另一个盒子,换起来就轻松多了。

MVVM 就是帮你把“界面”、“数据”、“逻辑”分开放进不同盒子的设计方法。

在移动开发中,我们常遇到这些问题:

  • 点击按钮没反应,不知道是 UI 没绑对,还是逻辑写错了
  • 后端接口一改,整个 App 崩了
  • 想加个新功能,结果要改 10 个文件

MVVM 能帮我们避免这些坑。它把代码分成三部分:

角色 职责 举个栗子
Model(模型) 负责数据,比如从后端获取用户信息 调 API、读数据库
View(视图) 用户看到的界面 按钮、文字、图片
ViewModel(视图模型) 连接 View 和 Model 的“翻译官” 把后端返回的 JSON 转成界面上能显示的文字

💡 关键思想:View 不直接碰 Model,一切通过 ViewModel 中转。


环境准备:5 分钟搭好开发环境

我们用 Android + Kotlin + Jetpack Compose 来演示(这是目前 Google 官方推荐的现代 Android 开发方式)。别担心,Kotlin 语法比 Java 简洁得多,新手友好!

步骤 1:安装 Android Studio

  1. 访问 developer.android.com/studio
  2. 下载最新版 Android Studio(选“Chipmunk”或更新版本)
  3. 安装时勾选 Android SDKAndroid Virtual Device

步骤 2:创建新项目

  1. 打开 Android Studio → “New Project”
  2. 选择 Empty Compose Activity
  3. 语言选 Kotlin
  4. Minimum SDK 设为 API 21 (Android 5.0)(覆盖 95% 以上设备)

步骤 3:添加必要依赖

打开 app/build.gradle 文件,在 dependencies 里加上:

implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"

这两行是 MVVM 的核心支持库,让 ViewModel 能和 Compose 无缝协作。

✅ 提示:如果 Gradle sync 失败,点右上角“Sync Now”就行。网络慢的话可以配阿里云镜像。


核心概念:用生活例子理解 MVVM

1. Model:你的“数据管家”

假设我们要做一个“用户资料页”,需要显示用户名和头像。
Model 就是那个负责去“后端”拿数据的人。

// User.kt
data class User(
    val id: Int,
    val name: String,
    val avatarUrl: String
)

// UserRepository.kt
class UserRepository {
    // 模拟从后端获取数据(实际项目这里会调 API)
    suspend fun fetchUser(userId: Int): User {
        // 模拟网络延迟
        delay(1000)
        return User(1, "小明", "https://example.com/avatar.jpg")
    }
}

🌟 注意:suspend 表示这是一个“挂起函数”,适合处理网络、数据库等耗时操作,不会卡住主线程。

2. ViewModel:界面和数据的“中间人”

View(界面)不能直接调 UserRepository,而是通过 ViewModel:

// UserViewModel.kt
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {

    private val _user = mutableStateOf<User?>(null)
    val user: State<User?> = _user

    init {
        loadUser()
    }

    private fun loadUser() {
        viewModelScope.launch {
            _user.value = repository.fetchUser(1)
        }
    }
}

关键点解释:

  • _user 是可变状态(MutableState),用来存储数据
  • user 是对外暴露的只读状态(State),View 只能读,不能改
  • viewModelScope.launch 在后台线程执行网络请求,安全又高效

💡 我当初学的时候总搞混 _useruser,记住:下划线的是“内部写”,没下划线的是“外部读”。

3. View:用户看到的一切

现在用 Compose 写界面,自动响应数据变化:

// MainActivity.kt
@Composable
fun UserProfileScreen(viewModel: UserViewModel) {
    val user by viewModel.user.collectAsState()

    if (user == null) {
        CircularProgressIndicator() // 加载中
    } else {
        Column {
            Text(text = "你好,${user!!.name}!")
            // 这里可以加 Image 显示头像
        }
    }
}

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                val viewModel: UserViewModel = hiltViewModel()
                UserProfileScreen(viewModel)
            }
        }
    }
}

神奇之处在于:只要 user 的值变了,界面自动刷新! 不用手动调 setText()


实战:做一个“天气查询”小应用

光看理论不过瘾,咱们动手做一个真实项目!

需求

  • 输入城市名
  • 点击“查询”按钮
  • 显示该城市的当前温度(模拟数据)

第一步:定义 Model

// Weather.kt
data class Weather(
    val city: String,
    val temperature: Int // 摄氏度
)

// WeatherRepository.kt
class WeatherRepository {
    suspend fun getWeather(city: String): Weather {
        delay(800) // 模拟网络请求
        // 实际项目这里会调后端 API,比如 https://api.weather.com/v1/...
        return Weather(city, (20..35).random())
    }
}

第二步:写 ViewModel

// WeatherViewModel.kt
@HiltViewModel
class WeatherViewModel @Inject constructor(
    private val repo: WeatherRepository
) : ViewModel() {

    private val _weather = mutableStateOf<Weather?>(null)
    val weather: State<Weather?> = _weather

    private val _isLoading = mutableStateOf(false)
    val isLoading: State<Boolean> = _isLoading

    fun searchWeather(city: String) {
        if (city.isBlank()) return
        _isLoading.value = true
        viewModelScope.launch {
            try {
                _weather.value = repo.getWeather(city)
            } catch (e: Exception) {
                // 实际项目这里要处理错误
            } finally {
                _isLoading.value = false
            }
        }
    }
}

第三步:搭建 View(Compose 界面)

@Composable
fun WeatherApp(viewModel: WeatherViewModel) {
    var cityInput by remember { mutableStateOf("") }
    val weather by viewModel.weather.collectAsState()
    val isLoading by viewModel.isLoading.collectAsState()

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        OutlinedTextField(
            value = cityInput,
            onValueChange = { cityInput = it },
            label = { Text("请输入城市") },
            modifier = Modifier.fillMaxWidth()
        )

        Spacer(modifier = Modifier.height(16.dp))

        Button(
            onClick = { viewModel.searchWeather(cityInput) },
            enabled = !isLoading,
            modifier = Modifier.fillMaxWidth()
        ) {
            if (isLoading) {
                CircularProgressIndicator(
                    color = Color.White,
                    modifier = Modifier.size(16.dp)
                )
            } else {
                Text("查询天气")
            }
        }

        Spacer(modifier = Modifier.height(24.dp))

        weather?.let { w ->
            Card(
                modifier = Modifier.fillMaxWidth(),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
            ) {
                Column(
                    modifier = Modifier.padding(16.dp),
                    horizontalAlignment = Alignment.CenterHorizontally
                ) {
                    Text(w.city, style = MaterialTheme.typography.headlineMedium)
                    Text("${w.temperature}°C", style = MaterialTheme.typography.displaySmall)
                }
            }
        }
    }
}

最后:连接一切

MainActivity 中注入并使用:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyAppTheme {
                val viewModel: WeatherViewModel = hiltViewModel()
                WeatherApp(viewModel)
            }
        }
    }
}

✅ 运行效果:输入“北京”,点查询,1 秒后显示“北京 28°C”。


新手常见问题 & 解决方案

Q1:为什么我的界面不刷新?

  • 原因:可能没用 StatecollectAsState()
  • 解决:确保 ViewModel 中的状态是 mutableStateOf,View 中用 by collectAsState() 读取

Q2:ViewModel 里怎么调后端 API?

  • 实际项目中,UserRepositoryWeatherRepository 会使用 Retrofit 或 Ktor 发起 HTTP 请求:
    interface ApiService {
        @GET("weather")
        suspend fun getWeather(@Query("city") city: String): WeatherResponse
    }
    
    这属于“后端交互”范畴,MVVM 的好处是:无论你用什么网络库,ViewModel 的接口不变!

Q3:Hilt 是什么?必须用吗?

  • Hilt 是依赖注入框架,帮你自动创建 UserRepository 并传给 ViewModel。
  • 初学可以手动 new 对象(不推荐长期使用):
    class UserViewModel : ViewModel() {
        private val repository = UserRepository()
        // ...
    }
    

Q4:MVVM 和 MVC、MVP 有什么区别?

简单对比:

架构 数据流向 适合场景
MVC View → Controller → Model → View Web 开发(如 Spring MVC)
MVP View ↔ Presenter ↔ Model 老 Android 项目(findViewById 时代)
MVVM View ↔ ViewModel ↔ Model 现代 Android / iOS(数据驱动 UI)

🎯 MVVM 的最大优势:UI 自动响应数据变化,无需手动同步。


学习建议:从“代码人生”走向专业开发

我当初也是从“Hello World”开始的。如果你刚入门,记住这三条:

  1. 先跑通,再优化
    别一上来就想设计完美架构。先把功能做出来,再用 MVVM 重构。

  2. 后端不是你的敌人
    很多新手怕“后端”,其实你只需要知道:后端给你 JSON,你解析成 Kotlin 对象就行。用工具如 JSON to Kotlin Class 自动生成代码。

  3. 每天写 20 行代码
    我坚持了三年,从实习生到大厂工程师。代码人生,贵在持续。

下一步学什么?

  • ✅ 掌握 Retrofit:连接真实后端 API
  • ✅ 学习 Room Database:本地数据持久化
  • ✅ 了解 Coroutines Flow:处理复杂数据流
  • ✅ 尝试 Navigation Compose:多页面跳转

结语

MVVM 不是魔法,而是一种 让代码更清晰、更易维护的思维方式。当你能把界面、逻辑、数据分离开,你就已经超越了 80% 的初学者。

希望这篇教程能帮你迈出架构设计的第一步。如果你觉得有用,欢迎去 B站 搜我的频道(ID:CodeLifeGuide),我会持续更新 “零基础到大厂”系列视频

记住:每一个大神,都曾是连 ViewModel 都拼不对的新手。 你的代码人生,现在开始书写!

本文完整代码已上传 GitHub:github.com/yourname/mvvm-starter
(替换 yourname 为你的用户名即可)

加油,未来的架构师!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝