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

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

文章目录

本文首发地址 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 来管理不同类型的状态:

kotlin 复制代码
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 数据库来提供离线支持和数据缓存:

kotlin 复制代码
@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
            }
        }
    }
}

类型转换器

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

kotlin 复制代码
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 实现钱包连接:

kotlin 复制代码
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 实现商品交易和支付:

kotlin 复制代码
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 来管理依赖注入,确保各层之间的解耦:

kotlin 复制代码
@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

科技美学设计系统

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

kotlin 复制代码
@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 组件:

kotlin 复制代码
@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 实现列表虚拟化:

kotlin 复制代码
@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 优化重组:

kotlin 复制代码
@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. 数据库优化

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

kotlin 复制代码
@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
)

测试策略

单元测试

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

kotlin 复制代码
// 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 测试:

kotlin 复制代码
@HiltAndroidTest
class 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

相关推荐
加速财经2 分钟前
WEEX从注册到首单:新手入门完整操作流程指南
区块链
不可描述的两脚兽43 分钟前
学习笔记《区块链技术与应用》第六天 问答 匿名技术 零知识证明
笔记·学习·区块链
DemonAvenger1 小时前
Go网络安全编程:TLS/SSL实践指南
网络协议·架构·go
狂浪天涯1 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 6 | 提交 GraphicBuffer 到 SurfaceFlinger
android·架构
来来走走1 小时前
Flutter开发 StatelessWidget与StatefulWidget基本了解
android·flutter
运维开发王义杰2 小时前
Ethereum:拥抱开源,OpenZeppelin 未来的两大基石 Relayers 与 Monitor
开源·web3·区块链·智能合约
毛小茛3 小时前
认识微服务
微服务·云原生·架构
洛卡卡了4 小时前
“改个配置还要发版?”搞个配置后台不好吗
前端·后端·架构
深盾安全4 小时前
Android SO导出符号的深度解析与安全防护指南
android