Kotlin打造完整电商APP (模块化+MVP+主流框架) - 实战课程

0 阅读6分钟

Kotlin打造完整电商APP (模块化+MVP+主流框架) ---xingkeit.top/7781/Kotlin 电商 APP 进阶开发:模块化 + MVP + 主流框架核心实战指南 在移动开发的“下半场”,简单的“能跑通”已无法满足市场需求。作为一名 Android 开发者,我深刻体会到,从“写代码”到“设计架构”的转变,是通往高级工程师的必经之路。本文将结合我的实战经验,探讨如何利用 Kotlin 的现代特性,结合模块化设计与 MVP 架构,构建一个健壮、可维护的电商 APP。 前言:为什么 Kotlin 是现代电商开发的最佳拍档? 电商 APP 的业务逻辑极其复杂:商品展示、购物车逻辑、订单支付、个人中心……每一个模块都充满了异步操作和状态变更。 在 Java 时代,我们需要大量的样板代码来处理这些逻辑。而 Kotlin 的出现,特别是其空安全特性、扩展函数以及协程,让代码变得极其简洁且安全。比如,处理网络请求的回调地狱在 Kotlin 协程面前简直不值一提。 💡 个人观点:Kotlin 不仅仅是一门语言,它是一种“生产力工具”。在电商开发中,利用 Kotlin 的 Data Class 生成 JSON 解析代码,利用协程处理并发任务,开发效率至少提升 50%。 一、 架构设计基石:MVP 与模块化的完美结合 1.1 为什么选择 MVP 而不是 MVVM? 虽然 Google 现在主推 Jetpack Compose + MVVM,但在大型电商 APP 的传统 View 体系中,我依然认为 MVP(Model-View-Presenter) 具有极高的实战价值: 清晰的职责划分:View 只负责 UI,Model 只负责数据,Presenter 负责逻辑调度。这在团队协作中非常重要,UI 开发者和逻辑开发者可以互不干扰。 较低的入门门槛:相比 MVVM 的 LiveData 和 DataBinding,MVP 的概念更直观,调试也更容易(Presenter 就是普通的 Java/Kotlin 类,方便打 Log)。 View 的完全解耦:在进行单元测试时,Mock View 接口非常简单。 1.2 模块化:摆脱“屎山”代码的终极方案 随着电商业务的迭代,代码量会呈指数级增长。如果所有代码都堆在 app 模块下,编译速度会慢到让人怀疑人生。模块化 势在必行。 推荐的模块划分方案: MyShopApp/ ├── app/ (主模块,负责组装) ├── module-main/ (首页模块:Activity、Fragment) ├── module-cart/ (购物车模块) ├── module-user/ (个人中心模块) ├── module-order/ (订单模块) ├── core/ (核心基础库) │ ├── network/ (网络封装) │ ├── common/ (工具类、常量) │ └── widget/ (通用自定义控件) └── provider/ (服务提供者,用于模块间通信) ⚠️ 注意:模块化不仅是为了物理隔离,更是为了逻辑解耦。核心模块不应依赖业务模块,业务模块之间也应通过接口或 ARouter/Reflection 等路由框架进行通信。 二、 核心实战:构建 MVP 基类与网络层 2.1 MVP 契约接口设计 在 Kotlin 中,我们可以充分利用接口来定义 MVP 的契约。以“商品详情页”为例: /**

  • 商品详情页 MVP 契约接口
  • 将 View 和 Presenter 的接口定义在一起,便于管理 / interface ProductDetailContract { // View 层:定义 UI 操作 interface View { fun onLoading() fun onSuccess(product: Product) fun onError(msg: String) fun onAddToCartSuccess() } // Presenter 层:定义业务逻辑 interface Presenter { fun getProductDetail(productId: Int) fun addToCart(productId: Int, skuId: String) } } 2.2 MVP 基类封装 为了避免重复代码,我们需要封装 BasePresenter 和 BaseActivity。 BasePresenter.kt package com.example.myshop.core.base open class BasePresenter { // 使用 WeakReference 防止内存泄漏 private var viewRef: WeakReference? = null fun attachView(view: V) { viewRef = WeakReference(view) } fun detachView() { viewRef?.clear() viewRef = null } // 获取 View,如果为空则返回 null,或者抛出异常(视项目规范而定) protected val view: V? get() = viewRef?.get() } BaseActivity.kt (配合 MVP) package com.example.myshop.core.base import android.os.Bundle import androidx.appcompat.app.AppCompatActivity abstract class BaseActivity<V, P : BasePresenter> : AppCompatActivity() { protected var presenter: P? = null // 子类必须实现提供 Presenter 的方法 abstract fun createPresenter(): P override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 假设这里绑定了布局 setContentView(getLayoutId()) // 初始化 Presenter presenter = createPresenter() presenter?.attachView(this as V) initView() initData() } override fun onDestroy() { super.onDestroy() // 解绑 View,防止内存泄漏 presenter?.detachView() presenter = null } abstract fun getLayoutId(): Int abstract fun initView() abstract fun initData() } 2.3 网络层封装:Retrofit + Coroutines + Repository 电商 APP 强依赖网络请求。使用 Retrofit 配合 Kotlin 协程 是目前的黄金组合。 ApiService.kt package com.example.myshop.core.network import retrofit2.http. interface ApiService { @GET("products/{id}") suspend fun getProductDetail(@Path("id") id: Int): ApiResponse @POST("cart/add") suspend fun addToCart(@Body request: AddCartRequest): ApiResponse } // 统一的数据响应格式 data class ApiResponse( val code: Int, val message: String, val data: T? ) // 数据模型 data class Product( val id: Int, val name: String, val price: Double, val imageUrl: String, val stock: Int ) ProductRepository.kt package com.example.myshop.core.repository import com.example.myshop.core.network.ApiResponse import com.example.myshop.core.network.ApiService import com.example.myshop.core.network.Product // Repository 负责数据的获取,无论是从网络还是本地缓存 class ProductRepository(private val apiService: ApiService) { suspend fun getProductDetail(id: Int): ApiResponse { return apiService.getProductDetail(id) } suspend fun addToCart(productId: Int, skuId: String): ApiResponse { return apiService.addToCart(AddCartRequest(productId, skuId)) } } data class AddCartRequest( val productId: Int, val skuId: String ) 三、 业务实战:商品详情页的逻辑实现 有了上面的基础架构,实现具体的业务页面就变得非常简单和清晰。 ProductDetailPresenter.kt package com.example.myshop.module.product import com.example.myshop.core.base.BasePresenter import com.example.myshop.core.network.ApiResponse import com.example.myshop.core.repository.ProductRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class ProductDetailPresenter( private val repository: ProductRepository ) : BasePresenter<ProductDetailContract.View>(), ProductDetailContract.Presenter { override fun getProductDetail(productId: Int) { // 在 IO 线程发起请求 GlobalScope.launch(Dispatchers.IO) { try { val result = repository.getProductDetail(productId) // 切换回主线程更新 UI withContext(Dispatchers.Main) { view?.onLoading() if (result.code == 200 && result.data != null) { view?.onSuccess(result.data!!) } else { view?.onError(result.message) } } } catch (e: Exception) { withContext(Dispatchers.Main) { view?.onError("网络异常: {e.message}") } } } } override fun addToCart(productId: Int, skuId: String) { GlobalScope.launch(Dispatchers.IO) { try { val result = repository.addToCart(productId, skuId) withContext(Dispatchers.Main) { if (result.code == 200) { view?.onAddToCartSuccess() } else { view?.onError("加入购物车失败") } } } catch (e: Exception) { withContext(Dispatchers.Main) { view?.onError("网络请求失败") } } } } } ProductDetailActivity.kt package com.example.myshop.module.product import android.os.Bundle import android.widget.Button import android.widget.ImageView import android.widget.TextView import android.widget.Toast import com.bumptech.glide.Glide import com.example.myshop.R import com.example.myshop.core.base.BaseActivity class ProductDetailActivity : BaseActivity<ProductDetailContract.View, ProductDetailPresenter>(), ProductDetailContract.View { private lateinit var tvName: TextView private lateinit var tvPrice: TextView private lateinit var ivImage: ImageView private lateinit var btnAddCart: Button override fun createPresenter(): ProductDetailPresenter { // 依赖注入 Repository(这里简化为直接 new) return ProductDetailPresenter(ProductRepository(ApiClient.getApiService())) } override fun getLayoutId(): Int = R.layout.activity_product_detail override fun initView() { tvName = findViewById(R.id.tv_product_name) tvPrice = findViewById(R.id.tv_product_price) ivImage = findViewById(R.id.iv_product_image) btnAddCart = findViewById(R.id.btn_add_cart) btnAddCart.setOnClickListener { // 模拟数据,实际应从页面状态获取 presenter?.addToCart(1001, "SKU_001") } } override fun initData() { // 获取传入的商品 ID val productId = intent.getIntExtra("product_id", 0) if (productId > 0) { presenter?.getProductDetail(productId) } } // --- MVP View 接口实现 --- override fun onLoading() { // 可以显示 Loading Dialog Toast.makeText(this, "加载中...", Toast.LENGTH_SHORT).show() } override fun onSuccess(product: Product) { tvName.text = product.name tvPrice.text = "¥{product.price}" Glide.with(this).load(product.imageUrl).into(ivImage) } override fun onError(msg: String) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() } override fun onAddToCartSuccess() { Toast.makeText(this, "已成功加入购物车", Toast.LENGTH_SHORT).show() } } 四、 模块间通信:路由设计 在模块化架构中,module-cart 不应该直接依赖 module-product,否则模块化就失去了意义。我们需要一个路由中心。 这里简单演示一下使用 显式 Intent + 反射 或 ARouter 的思路(以伪代码/逻辑描述为主): // 在 core 模块定义常量 object RouterPath { const val PATH_PRODUCT_DETAIL = "/product/detail" const val PATH_CART_MAIN = "/cart/main" } // 在 main 模块进行跳转 // Kotlin 扩展函数风格的跳转 fun navigateToProductDetail(context: Context, productId: Int) { val intent = Intent().apply { setClassName(context, "com.example.myshop.module.product.ProductDetailActivity") putExtra("product_id", productId) } context.startActivity(intent) } 在实际生产中,通常会引入 ARouter 或 TheRouter 等成熟框架,利用注解自动生成路由映射表,实现完全的解耦。 五、 总结与进阶思考 通过上述代码,我们构建了一个典型的模块化 + MVP + Kotlin 协程 + Retrofit 的电商 APP 基础框架。 这套架构的优势: 稳定性:MVP 的解耦让 UI 和逻辑互不干扰。 可读性:Kotlin 的简洁语法让代码读起来像散文。 可扩展性:模块化设计允许团队并行开发,新功能只需引入新模块。 进阶方向: 引入 Jetpack:使用 ViewModel 和 LiveData 替换部分 Presenter 的功能,或者完全迁移到 MVVM 架构。 Kotlin Flow:用 Flow 替换协程的简单 LiveData,处理更复杂的数据流。 KSP (Kotlin Symbol Processing):使用 KSP 替代 kapt/apt 进行注解处理,提升编译速度。 Compose:逐步将 UI 迁移到 Jetpack Compose,结合 MVI(Model-View-Intent)架构,这将带来更好的性能和开发体验。 这套技术栈不仅适用于电商 APP,也适用于大多数中大型企业级 Android 应用的开发。希望这份指南能为你的进阶之路提供有力的参考。