> 技术文档 > Android 区块链 + CleanArchitecture + MVI 架构实践_mvi实践

Android 区块链 + CleanArchitecture + MVI 架构实践_mvi实践


Android 区块链 + CleanArchitecture + MVI 架构实践

文章目录

  • Android 区块链 + CleanArchitecture + MVI 架构实践
    • 前言
    • 项目概述--核心特性
    • Clean Architecture 架构的三层分离设计理念
      • Presentation Layer(表现层)
      • Domain Layer(领域层)
      • Data Layer(数据层)
    • MVI 模式深度解析
      • 单向数据流的优势
      • 状态管理策略
    • Room 数据库架构设计
      • 数据库设计
      • 类型转换器
    • Solana Mobile SDK 集成
      • 钱包连接
      • 区块链交易
    • 依赖注入架构--Hilt 模块配置
    • UI 设计与 Jetpack Compose
      • 科技美学设计系统
      • 可复用组件设计
    • 性能优化策略
      • 1. Lazy Loading
      • 2. 状态管理优化
      • 3. 数据库优化
    • 测试策略
      • 单元测试
      • UI 测试
    • 架构优势总结
      • 1. 可维护性
      • 2. 可测试性
      • 3. 可扩展性
      • 4. 开发效率
    • 与其他架构的对比
    • 总结

本文首发地址 https://h89.cn/archives/420.html

前言

在区块链技术快速发展的今天,去中心化应用(DApp)正在重新定义传统的应用开发模式。本文将深入探讨如何在Android 平台上构建一个基于 Solana Mobile SDK 的去中心化电商平台,采用 Clean Architecture 和 MVI架构模式,实现高度解耦、可测试和可维护的现代化应用。

项目源码地址 在结尾

项目概述–核心特性

我们构建的是一个完整的 Web3 去中心化电商平台,集成了购买、销售、收益和治理四大核心功能。该项目不仅展示了区块链技术在电商领域的应用潜力,更重要的是展现了现代 Android 开发的最佳实践:

  • 去中心化交易: 基于 Solana 区块链的安全交易
  • 钱包集成: 无缝集成 Solana Mobile Wallet Adapter
  • 现代 UI: Jetpack Compose + Material Design 3
  • 响应式架构: MVI 模式确保单向数据流
  • 离线支持: Room 数据库提供本地数据持久化
  • 科技美学: 深色主题配合霓虹色彩的未来科技风格

Clean Architecture 架构的三层分离设计理念

我们严格遵循 Uncle Bob 的 Clean Architecture 原则,将应用分为三个独立的层次:

┌─────────────────────────────────────────────────────────┐│  Presentation Layer  ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Intent │ │ ViewModel │ │ State │ ││ │ │ │ │ │ │ ││ │ UserAction │→ │ StateFlow │→ │ UIState │ ││ │ SystemEvent │ │ SharedFlow │ │ Effect │ ││ └─────────────┘ └─────────────┘ └─────────────┘ │└─────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────┐│  Domain Layer ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Entity │ │ Use Case │ │ Repository │ ││ │ │ │ │ │ Interface │ ││ │ Product │ │ RegisterMer │ │ IMerchant │ ││ │ Order │ │ chantUseCase│ │ Repository │ ││ │ Merchant │ │ GetProducts │ │ IProduct │ ││ └─────────────┘ └─────────────┘ └─────────────┘ │└─────────────────────────────────────────────────────────┘ ↓┌─────────────────────────────────────────────────────────┐│  Data Layer ││ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││ │ Repository │ │ DataSource │ │ Mapper │ ││ │ Implement │ │ │ │ │ ││ │ │ │ Mock/Room │ │ DTO ↔ Entity│ ││ │ ProductRepo │ │ Solana/API │ │ Serialization│ ││ └─────────────┘ └─────────────┘ └─────────────┘ │└─────────────────────────────────────────────────────────┘

Presentation Layer(表现层)

表现层负责 UI 渲染和用户交互,采用 MVI 模式确保单向数据流:```kotlin// Intent - 用户意图的抽象表示sealed class ProductIntent { object LoadProducts : ProductIntent() data class SearchProducts(val query: String) : ProductIntent() data class FilterProducts(val filter: ProductFilter) : ProductIntent()}// State - 不可变的 UI 状态data class ProductState( val products: List = emptyList(), val isLoading: Boolean = false, val error: String? = null, val searchQuery: String = \"\")// ViewModel - 状态管理和业务逻辑协调class ProductViewModel @Inject constructor( private val getProductsUseCase: GetProductsUseCase) : ViewModel() { private val _state = MutableStateFlow(ProductState()) val state: StateFlow = _state.asStateFlow() fun handleIntent(intent: ProductIntent) { when (intent) { is ProductIntent.LoadProducts -> loadProducts() is ProductIntent.SearchProducts -> searchProducts(intent.query) is ProductIntent.FilterProducts -> filterProducts(intent.filter) } }}```

Domain Layer(领域层)

领域层包含纯粹的业务逻辑,不依赖任何框架:```kotlin// Entity - 业务实体data class Product( val id: String, val name: String, val description: String, val price: BigDecimal, val currency: String, val imageUrls: List, val sellerId: String, val category: String, val stock: Int, val rating: Float, val reviewCount: Int)// Use Case - 业务用例class GetProductsUseCase @Inject constructor( private val productRepository: IProductRepository) { suspend operator fun invoke(): Flow<Result<List>> { return productRepository.getProducts() .map { products -> Result.Success(products.sortedByDescending { it.rating }) } .catch { exception -> emit(Result.Error(exception)) } }}// Repository Interface - 数据访问抽象interface IProductRepository { suspend fun getProducts(): Flow<List> suspend fun getProductById(id: String): Product? suspend fun searchProducts(query: String): Flow<List>}```

Data Layer(数据层)

数据层实现具体的数据访问逻辑,支持多种数据源:```kotlin// Repository Implementationclass ProductRepositoryImpl @Inject constructor( private val mockDataSource: MockProductDataSource, private val roomDataSource: RoomProductDataSource, private val solanaDataSource: SolanaProductDataSource) : IProductRepository { override suspend fun getProducts(): Flow<List> { return combine( roomDataSource.getProducts(), solanaDataSource.getProducts() ) { localProducts, blockchainProducts -> // 合并本地和区块链数据 (localProducts + blockchainProducts).distinctBy { it.id } } }}// Data Source Abstractioninterface ProductDataSource { suspend fun getProducts(): Flow<List> suspend fun getProductById(id: String): Product? suspend fun insertProducts(products: List)}```

MVI 模式深度解析

单向数据流的优势

MVI 模式通过强制单向数据流,解决了传统 MVP/MVVM 模式中状态管理的复杂性:

User Interaction → Intent → ViewModel → State → UI → User Interaction

状态管理策略

我们使用 StateFlow 和 SharedFlow 来管理不同类型的状态:

class MainViewModel @Inject constructor( private val solanaWalletConnectUseCase: SolanaWalletConnectUseCase) : ViewModel() { // UI 状态 - 使用 StateFlow private val _uiState = MutableStateFlow(MainUiState()) val uiState: StateFlow<MainUiState> = _uiState.asStateFlow() // 一次性事件 - 使用 SharedFlow private val _uiEffect = MutableSharedFlow<UiEffect>() val uiEffect: SharedFlow<UiEffect> = _uiEffect.asSharedFlow() fun handleIntent(intent: MainIntent) { viewModelScope.launch { when (intent) { is MainIntent.ConnectWallet -> connectWallet() is MainIntent.DisconnectWallet -> disconnectWallet() } } }}

Room 数据库架构设计

数据库设计

我们采用 Room 数据库来提供离线支持和数据缓存:

@Database( entities = [ProductEntity::class], version = 1, exportSchema = false)@TypeConverters(StringListConverter::class, StringMapConverter::class)abstract class AppDatabase : RoomDatabase() { abstract fun productDao(): ProductDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder(  context.applicationContext,  AppDatabase::class.java,  \"web3_ecommerce_database\" ).build() INSTANCE = instance instance } } }}

类型转换器

为了支持复杂数据类型的存储,我们实现了自定义类型转换器:

class StringListConverter { @TypeConverter fun fromStringList(value: List<String>): String { return Json.encodeToString(value) } @TypeConverter fun toStringList(value: String): List<String> { return Json.decodeFromString(value) }}class StringMapConverter { @TypeConverter fun fromStringMap(value: Map<String, String>): String { return Json.encodeToString(value) } @TypeConverter fun toStringMap(value: String): Map<String, String> { return Json.decodeFromString(value) }}

Solana Mobile SDK 集成

钱包连接

我们通过 Solana Mobile Wallet Adapter 实现钱包连接:

class SolanaWalletConnectUseCase @Inject constructor( private val mobileWalletAdapter: MobileWalletAdapter) { suspend fun connect(): Result<Connected> { return try { val result = mobileWalletAdapter.transact { sender -> sender.authorize(  identityUri = Uri.parse(\"https://web3-ecommerce.app\"),  iconUri = Uri.parse(\"favicon.ico\"),  identityName = \"Web3 Ecommerce\" ) } Result.Success(Connected(result.publicKey, result.accountLabel)) } catch (e: Exception) { Result.Error(e) } }}

区块链交易

通过 Solana SDK 实现商品交易和支付:

class SolanaTransactionUseCase @Inject constructor( private val mobileWalletAdapter: MobileWalletAdapter, private val solanaTokenUtils: SolanaTokenUtils) { suspend fun purchaseProduct( productId: String, amount: BigDecimal, sellerAddress: String ): Result<String> { return try { val transaction = solanaTokenUtils.createTransferTransaction( amount = amount, recipientAddress = sellerAddress ) val result = mobileWalletAdapter.transact { sender -> sender.signAndSendTransactions(arrayOf(transaction)) } Result.Success(result.signatures.first()) } catch (e: Exception) { Result.Error(e) } }}

依赖注入架构–Hilt 模块配置

我们使用 Hilt 来管理依赖注入,确保各层之间的解耦:

@Module@InstallIn(SingletonComponent::class)abstract class DataModule { @Binds abstract fun bindProductRepository( productRepositoryImpl: ProductRepositoryImpl ): IProductRepository @Binds abstract fun bindMerchantRepository( merchantRepositoryImpl: MerchantRepositoryImpl ): IMerchantRepository}@Module@InstallIn(SingletonComponent::class)object AppModule { @Provides @Singleton fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase { return AppDatabase.getDatabase(context.applicationContext) } @Provides fun provideProductDao(database: AppDatabase): ProductDao { return database.productDao() } @Provides @Singleton fun provideMobileWalletAdapter(@ApplicationContext context: Context): MobileWalletAdapter { return MobileWalletAdapter() }}

UI 设计与 Jetpack Compose

科技美学设计系统

我们采用深色主题配合霓虹色彩,营造未来科技氛围:

@Composablefun Web3EcommerceTheme( darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { val colorScheme = if (darkTheme) { darkColorScheme( primary = TechBlue, secondary = NeonGreen, tertiary = NeonPurple, background = DeepBlack, surface = DarkGray, onPrimary = Color.White, onSecondary = Color.Black, onBackground = Color.White, onSurface = Color.White ) } else { lightColorScheme() } MaterialTheme( colorScheme = colorScheme, typography = TechTypography, content = content )}

可复用组件设计

我们创建了一系列可复用的 Compose 组件:

@Composablefun TechButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) { Button( onClick = onClick, modifier = modifier .glowEffect(enabled) .clip(RoundedCornerShape(8.dp)), enabled = enabled, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.onPrimary ) ) { Text( text = text, style = MaterialTheme.typography.labelLarge ) }}@Composablefun ProductCard( product: Product, onProductClick: (String) -> Unit, modifier: Modifier = Modifier) { Card( modifier = modifier .fillMaxWidth() .clickable { onProductClick(product.id) } .glowEffect(), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface ) ) { Column( modifier = Modifier.padding(16.dp) ) { AsyncImage( model = product.imageUrls.firstOrNull(), contentDescription = product.name, modifier = Modifier  .fillMaxWidth()  .height(200.dp)  .clip(RoundedCornerShape(8.dp)), contentScale = ContentScale.Crop ) Spacer(modifier = Modifier.height(8.dp)) Text( text = product.name, style = MaterialTheme.typography.titleMedium, maxLines = 2, overflow = TextOverflow.Ellipsis ) Text( text = \"${product.price} ${product.currency}\", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary ) } }}

性能优化策略

1. Lazy Loading

使用 LazyColumn 和 LazyGrid 实现列表虚拟化:

@Composablefun ProductGrid( products: List<Product>, onProductClick: (String) -> Unit, modifier: Modifier = Modifier) { LazyVerticalGrid( columns = GridCells.Fixed(2), modifier = modifier, contentPadding = PaddingValues(16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(products) { product -> ProductCard( product = product, onProductClick = onProductClick ) } }}

2. 状态管理优化

使用 remember 和 derivedStateOf 优化重组:

@Composablefun ProductScreen( viewModel: ProductViewModel = hiltViewModel()) { val state by viewModel.state.collectAsState() // 使用 derivedStateOf 避免不必要的重组 val filteredProducts by remember { derivedStateOf { state.products.filter { product -> product.name.contains(state.searchQuery, ignoreCase = true) } } } LaunchedEffect(Unit) { viewModel.handleIntent(ProductIntent.LoadProducts) } ProductGrid( products = filteredProducts, onProductClick = { productId -> // 导航到产品详情 } )}

3. 数据库优化

通过索引和查询优化提升数据库性能:

@Entity( tableName = \"products\", indices = [ Index(value = [\"category\"]), Index(value = [\"seller_id\"]), Index(value = [\"price\"]) ])data class ProductEntity( @PrimaryKey val id: String, val name: String, val description: String, val price: Double, val currency: String, @ColumnInfo(name = \"image_urls\") val imageUrls: List<String>, @ColumnInfo(name = \"seller_id\") val sellerId: String, val category: String, val stock: Int, val rating: Float, @ColumnInfo(name = \"review_count\") val reviewCount: Int, @ColumnInfo(name = \"created_at\") val createdAt: Long, @ColumnInfo(name = \"updated_at\") val updatedAt: Long)

测试策略

单元测试

我们为每一层都编写了相应的单元测试:

// Domain Layer 测试class GetProductsUseCaseTest { @Mock private lateinit var productRepository: IProductRepository private lateinit var getProductsUseCase: GetProductsUseCase @Before fun setup() { MockitoAnnotations.openMocks(this) getProductsUseCase = GetProductsUseCase(productRepository) } @Test fun `should return sorted products by rating`() = runTest { // Given val products = listOf( createProduct(id = \"1\", rating = 4.0f), createProduct(id = \"2\", rating = 4.5f), createProduct(id = \"3\", rating = 3.5f) ) whenever(productRepository.getProducts()).thenReturn(flowOf(products)) // When val result = getProductsUseCase().first() // Then assertTrue(result is Result.Success) val sortedProducts = (result as Result.Success).data assertEquals(\"2\", sortedProducts.first().id) assertEquals(\"3\", sortedProducts.last().id) }}

UI 测试

使用 Compose Testing 进行 UI 测试:

@HiltAndroidTestclass ProductScreenTest { @get:Rule val hiltRule = HiltAndroidRule(this) @get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>() @Test fun should_display_products_when_loaded() { // Given val products = listOf( createProduct(name = \"Test Product 1\"), createProduct(name = \"Test Product 2\") ) // When composeTestRule.setContent { Web3EcommerceTheme { ProductScreen() } } // Then composeTestRule.onNodeWithText(\"Test Product 1\").assertIsDisplayed() composeTestRule.onNodeWithText(\"Test Product 2\").assertIsDisplayed() }}

架构优势总结

1. 可维护性

  • 模块化设计: 各层独立,职责清晰
  • 依赖倒置: 高层模块不依赖低层模块
  • 接口抽象: 通过接口实现解耦

2. 可测试性

  • 依赖注入: 便于 Mock 和单元测试
  • 纯函数: Domain Layer 易于测试
  • UI 测试: Compose Testing 支持完整测试

3. 可扩展性

  • 插件化架构: 数据源可灵活切换
  • 功能模块: 新功能独立开发
  • 多平台支持: 架构支持扩展

4. 开发效率

  • 并行开发: 各层可并行开发
  • 快速迭代: Mock 数据支持快速原型
  • 离线开发: Room 数据库支持离线访问
  • 类型安全: Kotlin 类型系统减少错误

与其他架构的对比

特性 Clean Architecture MVI (Model-View-Intent) MVVM MVP MVC 核心思想 关注点分离,依赖反转,将业务逻辑与框架解耦。 单向数据流 (Unidirectional Data Flow),通过不可变状态管理 UI。 响应式数据绑定,View 自动随 ViewModel 变化而更新。 View 与 Presenter 严格分离,Presenter 负责所有逻辑。 业务逻辑与 UI 分离,Controller 作为中介。 复杂度 (涉及多层抽象和严格规则) 中高 (需处理 Intent 和 State 管理) (ViewModel 和数据绑定) (需要定义 View 和 Presenter 接口) (模式简单直接) 可测试性 极高 (核心业务逻辑完全独立) 极高 (UI 状态可预测,逻辑在 ViewModel 中) (ViewModel 独立于 View 测试) (Presenter 独立于 View 测试) (逻辑常与 View 耦合) 学习成本 (概念多,如 Use Case、Repository) 中高 (需理解单向数据流和不可变状态) (需理解 LiveData/StateFlow 和数据绑定) (接口通信和生命周期管理) 适用项目 大型复杂、企业级应用,需要长期维护和扩展。 中大型,需要明确、可预测的 UI 状态管理。 中大型,适合需要响应式UI和数据绑定的项目。 中型,适用于业务逻辑较复杂的项目。 小型、简单的应用或作为入门学习。 维护成本 低(长期),易于修改、扩展,影响范围小。 低(长期),状态可预测,调试方便。 ,ViewModel 可能会变得臃肿。 ,Presenter 可能会变得臃肿。 高(长期),代码容易混乱,难以维护。

总结

通过采用 Clean Architecture + MVI 架构模式,结合 Solana Mobile SDK、Jetpack Compose 和 Room 数据库等现代技术栈,我们成功构建了一个高质量的去中心化电商应用。这个架构不仅确保了代码的可维护性和可测试性,还为未来的功能扩展和技术演进奠定了坚实的基础。

在 Web3 时代,这样的架构设计将成为构建复杂去中心化应用的标准模式。通过合理的分层设计、清晰的依赖关系和现代化的开发工具,我们能够在保证用户体验的同时,充分发挥区块链技术的优势。


项目地址: https://github.com/chenjim/Web3-MVI-android

技术栈: Kotlin, Jetpack Compose, Solana Mobile SDK, Room Database, Hilt, MVI, Clean Architecture