当前位置: 首页 > news >正文

Android 区块链 + CleanArchitecture + 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<Product> = 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<ProductState> = _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<String>,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<Product>>> {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<Product>>suspend fun getProductById(id: String): Product?suspend fun searchProducts(query: String): Flow<List<Product>>
}
```

Data Layer(数据层)

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

MVI 模式深度解析

单向数据流的优势

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

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

状态管理策略

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

class MainViewModel @Inject constructor(private val solanaWalletConnectUseCase: SolanaWalletConnectUseCase
) : ViewModel() {// UI 状态 - 使用 StateFlowprivate val _uiState = MutableStateFlow(MainUiState())val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()// 一次性事件 - 使用 SharedFlowprivate 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(): ProductDaocompanion object {@Volatileprivate var INSTANCE: AppDatabase? = nullfun getDatabase(context: Context): AppDatabase {return INSTANCE ?: synchronized(this) {val instance = Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"web3_ecommerce_database").build()INSTANCE = instanceinstance}}}
}

类型转换器

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

class StringListConverter {@TypeConverterfun fromStringList(value: List<String>): String {return Json.encodeToString(value)}@TypeConverterfun toStringList(value: String): List<String> {return Json.decodeFromString(value)}
}class StringMapConverter {@TypeConverterfun fromStringMap(value: Map<String, String>): String {return Json.encodeToString(value)}@TypeConverterfun 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 {@Bindsabstract fun bindProductRepository(productRepositoryImpl: ProductRepositoryImpl): IProductRepository@Bindsabstract fun bindMerchantRepository(merchantRepositoryImpl: MerchantRepositoryImpl): IMerchantRepository
}@Module
@InstallIn(SingletonComponent::class)
object AppModule {@Provides@Singletonfun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {return AppDatabase.getDatabase(context.applicationContext)}@Providesfun provideProductDao(database: AppDatabase): ProductDao {return database.productDao()}@Provides@Singletonfun provideMobileWalletAdapter(@ApplicationContext context: Context): MobileWalletAdapter {return MobileWalletAdapter()}
}

UI 设计与 Jetpack Compose

科技美学设计系统

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

@Composable
fun 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 组件:

@Composable
fun 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)}
}@Composable
fun 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 实现列表虚拟化:

@Composable
fun 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 优化重组:

@Composable
fun 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 {@Mockprivate lateinit var productRepository: IProductRepositoryprivate lateinit var getProductsUseCase: GetProductsUseCase@Beforefun setup() {MockitoAnnotations.openMocks(this)getProductsUseCase = GetProductsUseCase(productRepository)}@Testfun `should return sorted products by rating`() = runTest {// Givenval 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))// Whenval result = getProductsUseCase().first()// ThenassertTrue(result is Result.Success)val sortedProducts = (result as Result.Success).dataassertEquals("2", sortedProducts.first().id)assertEquals("3", sortedProducts.last().id)}
}

UI 测试

使用 Compose Testing 进行 UI 测试:

@HiltAndroidTest
class ProductScreenTest {@get:Ruleval hiltRule = HiltAndroidRule(this)@get:Ruleval composeTestRule = createAndroidComposeRule<MainActivity>()@Testfun should_display_products_when_loaded() {// Givenval products = listOf(createProduct(name = "Test Product 1"),createProduct(name = "Test Product 2"))// WhencomposeTestRule.setContent {Web3EcommerceTheme {ProductScreen()}}// ThencomposeTestRule.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 ArchitectureMVI (Model-View-Intent)MVVMMVPMVC
核心思想关注点分离,依赖反转,将业务逻辑与框架解耦。单向数据流 (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

http://www.lryc.cn/news/609649.html

相关文章:

  • IDA9.1使用技巧(安装、中文字符串显示、IDA MCP服务器详细部署和MCP API函数修改开发经验)
  • Android工程命令行打包并自动生成签名Apk
  • 服务器突然之间特别卡,什么原因?
  • ffmpeg下载windows教程
  • clickhouse 中文数据的正则匹配
  • 随笔之 ClickHouse 列式分析数据库安装注意事项及基准测试
  • 人大金仓数据库常见问题(持续更新)
  • 数据结构----排序
  • Android 15.0 启动app时设置密码锁(升级到framework层判断)
  • 《时间之隙:内存溢出》
  • 《基于电阻抗断层成像(EIT)的触觉传感器:物理模拟与机器学习的创新结合》论文解读
  • RocketMQ与Kafka 消费者组的‌重平衡操作消息顺序性对比
  • 实现建筑环境自动控制,楼宇自控技术提升舒适与安全
  • 【前端】三件套基础介绍
  • 规则方法关系抽取-笔记总结
  • Postman 四种请求体格式全解析:区别、用法及 Spring Boot 接收指南
  • 实习005 (web后端springboot)
  • 【后端】Java static 关键字详解
  • 从零开始搞定类与对象(中)
  • Matplotlib与PySide6兼容性问题及解决方案
  • open-webui pipelines报404, ‘Filter pipeline.exporter not found‘
  • 基于Express+Ejs实现带登录认证的多模块增删改查后台管理系统
  • C++ 浅谈Robin Hood Hash 算法
  • 3ds Max 渲染效率提升指南:从场景设计优化开始
  • 【0基础3ds Max】常用快捷键
  • 【Linux下Java应用自动重启守护教程】
  • 【大模型】3D因果卷积动图怎么画
  • Linux—yum仓库及NFS网络共享服务
  • [QMT量化交易小白入门]-七十六、从tick数据中获取高频交易的量价背离信号
  • 验证码等待时间技术在酒店自助入住、美容自助与社区场景中的应用必要性研究—仙盟创梦IDE