Android KMP 快速入门2 - Koin依赖注入

news/2024/10/4 5:35:13 标签: android

这里写目录标题

    • 代码仓库
    • KMP 框架
      • 基本框架
      • actual&expect
      • Koin 依赖注入管理

代码仓库

本小节代码已经上传到gitee,请自行查看:
点击访问仓库


KMP 框架

基本框架

源码集合描述存放内容示例
androidMain针对 Android 平台的代码使用 Android SDK、Android 特定的 API 和 UI 组件
desktopMain针对桌面平台(Windows、macOS、Linux)的代码使用 JavaFX、Swing 或其他桌面 GUI 框架的代码
commonMain跨平台的用户界面代码,使用 Compose Multiplatform 框架定义可在 Android、iOS、桌面等多个平台上共享的 UI 组件
nativeMain原生代码,直接访问底层系统 API 或使用特定于平台的库的代码使用 POSIX 接口、平台特定的库或服务

actual&expect

KMP使用两个关键词actual和expect实现了跨平台开发;

  1. expect一般在commonApp里面定义,这是整个项目的通用逻辑部分,它相当于一个抽象类;
  2. actual一般在desktopApp或androidApp里面定义,它是对expect定义的函数或者变量的具体实现,这样就可以实现了每一个平台都有不同的各自对应的处理函数逻辑;

例如,先在commonApp下创建一个文件 BatteryManager.kt

该文件定义了获取当前设备电量剩余多少的方法;

expect class BatteryManager {
  fun getBatteryLevel(): Int
}

之后就到各个平台的实现层进行该方法的具体逻辑实现;

比如我在androidApp层实现了该方法,同样的,你需要在对应的位置创建一样名称的文件,用来对该expect定义的类进行相应的actual实现(你可以使用AndroidStudio的自动创建功能实现这一步骤)

/**
 * 实现电池管理功能的类
 *
 * @param context 应用程序上下文,用于获取电池信息
 */
actual class BatteryManager(
  private val context: Context
) {
  /**
   * 获取电池电量百分比
   *
   * 通过广播接收器获取电池状态,并计算电池电量百分比
   *
   * @return 电池电量百分比(0-100)
   */
  actual fun getBatteryLevel(): Int {
    // 创建一个意图过滤器,用于匹配电池状态改变的广播
    val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
    // 使用广播接收器接收电池状态的广播,这里不需要创建一个具体的接收器对象
    val batteryStatus = context.registerReceiver(null, intentFilter)
    // 从广播意图中获取电池电量级别,如果没有提供则默认为-1
    val level = batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
    // 从广播意图中获取电池电量的缩放值,如果没有提供则默认为-1
    val scale = batteryStatus?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1

    // 计算并返回电池电量百分比
    return (level / scale.toFloat() * 100).roundToInt()
  }
}
actual class BatteryManager {
  actual fun getBatteryLevel(): Int {
    val systemInfo = SystemInfo()
    val batteryLevel = systemInfo.hardware.powerSources.firstOrNull()

    return batteryLevel?.remainingCapacityPercent?.times(100)?.roundToInt() ?: -1
  }
}

Koin 依赖注入管理

此部分使用koin框架实现基本的依赖注入管理,采用MVVM架构

参考视频:Full Guide to Dependency Injection With Koin for Compose Multiplatform - KMP for Beginners (youtube.com)

下图展示了下面案例使用Koin框架的整体逻辑架构图,下面对逻辑进行简要陈述

  1. startKoin用于初始化整个Koin框架,在这里需要定义配置以及注册对应的modules模块
  2. 每一个module都管理者多个repository以及viewmodel
  3. 下图中sharedModule模块注册了DbRepo单例,并同时注册了DbVM
  4. DbRepo相当于MVC的Service层,用于仓储等底层逻辑的操作,他有一个对应的实现类DbRepoImpl
  5. DbVM相当于MVC的controller层,用于调用Service层内包装好的逻辑方法
  6. 所以最终我们的App视图实际上是自动注入DbVM后,通过该viewmodel调用对应的方法来执行对应的结果的(具体的代码思路可以参考Springboot的DI思想,这里不做过多阐述)

commonApp层

在KMP开发过程中,所有逻辑都必须先从commonApp层定义,然后再扩展到各个平台实现层来完善具体代码;

首先导入koin依赖,因为我们这边使用DSL进行依赖管理,所以需要先在libs.version.toml文件定义好对应依赖的版本以及名称;

[versions]
koin = "3.6.0-Beta4"
koinComposeMultiplatform = "1.2.0-Beta4"
navigationCompose = "2.8.0-alpha02"
lifecycleViewModel = "2.8.2"

[libraries]
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koinComposeMultiplatform" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koinComposeMultiplatform" }
navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }

然后再build.gradle.kts里面为各个层导入对应的依赖(只添加我在下面添加的依赖,其他的不用管)

sourceSets {
    val desktopMain by getting

    androidMain.dependencies {
      implementation(libs.koin.android)
      implementation(libs.koin.androidx.compose)
    }
    commonMain.dependencies {
      api(libs.koin.core)
      implementation(libs.koin.compose)
      implementation(libs.koin.compose.viewmodel)
      implementation(libs.navigation.compose)
    }
    desktopMain.dependencies {
    }
  }

这是commonApp层的所有代码,请遵照上方给出的逻辑架构图进行理解!!!

初始化koin

/**
 * 初始化Koin依赖注入框架
 *
 * 该函数用于在应用启动时设置Koin依赖注入框架的配置,并开始定义注入模块
 * 它允许传递一个自定义配置函数,以便于在不同项目中进行特定的配置调整
 *
 * @param config 一个可选的自定义配置函数,用于在启动Koin时进行额外配置
 *               该函数接收一个KoinApplicationBuilder作为参数,通过它可以自定义Koin的配置
 */
fun initKoin(config: KoinAppDeclaration? = null) {
    // 启动Koin,并在其配置过程中注入自定义配置(如果提供)以及定义好的注入模块
    startKoin {
        // 如果提供了自定义配置函数,则执行该函数,允许开发者对Koin进行特定的配置定制
        config?.invoke(this)
        // 注册应用所需的注入模块,这些模块包含应用中所有需要进行依赖注入的类和它们的创建逻辑
        modules(sharedModule, platformModule)
    }
}

定义两个Modules

expect val platformModule: Module

val sharedModule = module {
    singleOf(::DbRepoImpl).bind<DbRepo>()
    viewModelOf(::DbVM)
}

定义全局唯一的客户端实例,用来标注koin的唯一性

expect class DbClient

定义viewmodel

class DbVM(
  private val repository: DbRepo
) : ViewModel() {

  fun getHelloWorldString(): String {
    return repository.helloWorld()
  }
}

定义仓储接口DbRepo及其对应的实现类DbRepoImpl

interface DbRepo {
  fun helloWorld(): String
}

class DbRepoImpl(
  private val dbClient: DbClient
) : DbRepo {
  override fun helloWorld(): String {
    return "Hello World!"
  }
}

最后在app文件内调用DbVM,来显示简单的字段,嘻嘻

@OptIn(KoinExperimentalAPI::class)
@Composable
@Preview
fun App() {
  MaterialTheme {
    KoinContext {
      NavHost(
        navController = rememberNavController(),
        startDestination = "home"
      ) {
        composable(route = "home") {
          val viewModel = koinViewModel<DbVM>()
          Box(
            modifier = Modifier
              .fillMaxSize(),
            contentAlignment = Alignment.Center
          ) {
            Text(
              text = viewModel.getHelloWorldString()
            )
          }
        }
      }
    }
  }
}

androidmanifest.xml 配置文件设置

在该配置文件内,添加字段 android:name,他表示Koin的程序入口点,我们待会在androidApp层需要编写一个名称完全一致的方法,并在该方法内调用initKoin,来实现koin框架的初始化,以便在整个App最终启动完毕前就完成了DI;

<application
      android:name=".MyApplication"

      android:allowBackup="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@android:style/Theme.Material.Light.NoActionBar">
    <activity
        android:name=".MainActivity"
        android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>

androidApp层

首先需要处理程序入口点的问题

class MyApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    initKoin {
      androidContext(this@MyApplication)
    }
  }
}

初始化全局实例

actual class DbClient(
  private val context: Context
)

我们之前定义的platformModule模块直接用于存储DbClient的单例对象

actual val platformModule = module {
  singleOf(::DbClient)
}

http://www.niftyadmin.cn/n/5689511.html

相关文章

初始Kafka

1、Kafka是什么&#xff1f; Kafka是由Scala语言开发的一个多分区、多副本&#xff0c;基于Zookeeper集群协调的系统。 那这个所谓的系统又是什么系统呢&#xff1f; 回答这个问题要从发展的角度来看&#xff1a;起初Kafka的定位是分布式消息系统。但是目前它的定位是一个分布…

【web安全】——XSS漏洞

1.XSS漏洞基础 1.1.漏洞成因 XSS(Cross-site scripting)被称为跨站脚本攻击&#xff0c;由于与层叠样式表的缩写一样&#xff0c;因此被缩写为XSS.XSS漏洞形成的原因是网站/程序对前端用户的输入过滤不严格&#xff0c;导致攻击者可以将恶意的is/html代码注入到网页中&#x…

PostgreSQL 任意命令执行漏洞(CVE-2019-9193)

记一次授权攻击通过PostgreSql弱口令拿到服务器权限的事件。 使用靶机复现攻击过程。 过程 在信息收集过程中&#xff0c;获取到在公网服务器上开启了5432端口&#xff0c;尝试进行暴破&#xff0c;获取到数据库名为默认postgres&#xff0c;密码为1 随后连接进PostgreSql …

时间相关数据的统计分析(笔记更新中)

对事件相关数据的统计思路做一个笔记 可以用作肿瘤生长曲线&#xff08;Tumor Growth Curve&#xff09;/某一个药物处理后不同时间点表型的获取类型的数据。 总体来说合适的有两类&#xff0c;一类是以ANOVA为基础的方差分析&#xff0c;重复测量资料的方差分析&#xff1b;…

计算机毕业设计Hadoop+Spark知识图谱体育赛事推荐系统 体育赛事热度预测系统 体育赛事数据分析 体育赛事可视化 体育赛事大数据 大数据毕业设计

《HadoopSpark知识图谱体育赛事推荐系统》开题报告 一、研究背景与意义 随着互联网技术的迅猛发展和大数据时代的到来&#xff0c;体育赛事数据的数量呈爆炸式增长。用户面对海量的体育赛事信息&#xff0c;常常感到信息过载&#xff0c;难以快速找到感兴趣的赛事内容。传统的…

PostgreSQL升级:使用pg_upgrade进行大版本(16.3)升级(17.0)

1.pg_upgrade工具介绍 pg_upgrade 会创建新的系统表&#xff0c;并以重用旧的数据文件的方式进行升级。 pg_upgrade 的参数选项如下&#xff1a; -b bindir&#xff0c;--old-bindirbindir&#xff1a;旧的 PostgreSQL 可执行文件目录&#xff1b; -B bindir&#xff0c;--new-…

【PostgreSQL】提高篇——公用表表达式(CTE)和窗口函数

在这篇文章中&#xff0c;我将详细介绍 PostgreSQL 中的公用表表达式&#xff08;CTE&#xff09;和窗口函数&#xff0c;帮助你理解如何使用它们进行复杂的数据分析。我将通过具体的示例来演示这些概念的实际应用&#xff0c;并在每个示例中提供详细的解释和注释。 1. 公用表…

鸿蒙 HarmonyNext 与 Flutter 的异同之处

HarmonyNext 是华为推出的面向未来的应用开发框架&#xff0c;依托于鸿蒙&#xff08;HarmonyOS&#xff09;生态系统&#xff0c;特别适用于多设备协同、物联网&#xff08;IoT&#xff09;等场景。Flutter 是 Google 开发的跨平台 UI 框架&#xff0c;旨在通过单套代码运行在…