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

news2024/11/27 21:04:21

这里写目录标题

    • 代码仓库
    • 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.coloradmin.cn/o/2186988.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python、C++、java阶乘算法

最近&#xff0c;我除了Python还学了C和Java&#xff0c;然后在网上看到编程考题&#xff1a;阶乘。 首先&#xff0c;我们先理解什么是阶乘。 阶乘是数学中的一个概念&#xff0c;通常定义为从1乘到指定的数。具体来说&#xff0c;一个正整数的阶乘&#xff08;记作n!&#…

【课程学习】Wireless Communications

Goldsmith A. Wireless communications[M]. Cambridge university press, 2005. Wireless Communications 无线通信课程 文章目录 2-Path Loss, Shadowing, and Multipath2.4-Two-Ray Multipath Model时延扩展 delay spread P33 3-Statistical Multipath Channel Models3.3-Wid…

Python+Matplotlib创建高等数学上册P2页例3交互动画

import numpy as np import matplotlib.pyplot as plt from matplotlib.widgets import Slider from matplotlib.patches import Rectangle# 创建图形和坐标轴 fig, ax plt.subplots(figsize(12, 8)) plt.subplots_adjust(bottom0.2)# 设置坐标轴范围 ax.set_xlim(-2*np.pi, 2…

BugReport中的App Processor wakeup字段意义

一、功耗字段意义&#xff1a; App processor wakeup:Netd基于xt_idletimer 待机下监视网络设备的收发工作状态&#xff0c;即当设备发生联网从休眠态变成为唤醒态时&#xff0c;会记录打醒者的uid(uid大于0)和网络类型(wifi或数据类型)、时间戳 实际日志&#xff1a;我们在B…

【C++复习】C++11经典语法

文章目录 {}列表初始化1. 初始化内置类型变量2. 初始化数组3. 初始化标准容器4. 初始化自定义类型5. 构造函数初始化列表6. 初始化列表&#xff08;initializer_list&#xff09;7. 返回值初始化8. 静态成员变量和全局变量的就地初始化9. 防止类型收窄总结 decltype右值引用完美…

图像处理案例04

图像处理 问题&#xff1a;把不规则的图片按照参考图摆放 步骤&#xff1a; 1. 用ORB找关键点 2. 关键点匹配 3. 根据上一步匹配的关键点得出单应性矩阵 4. 根据单应性矩阵对不规则进行透视变换 import cv2 import numpy as np import matplotlib.pyplot as pltimgl cv2.imrea…

精华帖分享 | 因子构建思考1

本文来源于量化小论坛股票量化板块精华帖&#xff0c;作者为z-coffee。 以下为精华帖正文&#xff1a; 一段时间没写帖子&#xff0c;其实一直在研究策略&#xff0c;只是从不同的角度去思考而已。熟悉我的老板其实清楚&#xff0c;我的炉子水平一般&#xff0c;基本不太依托…

什么是 Web 应用中的 Facet 控件

在 Web 页面设计和开发中&#xff0c;facet 是一个十分重要的概念&#xff0c;尤其在电子商务、数据搜索和筛选功能中非常常见。Facet 通常指的是一种分类或过滤的方式&#xff0c;用于让用户能够通过多维度的条件来细化和调整数据结果&#xff0c;从而找到更符合需求的内容。F…

资源《Arduino 扩展板5-单电机驱动》说明。

资源链接&#xff1a; Arduino 扩展板5-单电机驱动 1.文件明细&#xff1a; 2.文件内容说明 包含&#xff1a;AD工程、原理图、PCB。 3.内容展示 4.简述 该文件为PCB工程&#xff0c;采用AD做的。 该文件打板后配合Arduino使用&#xff0c;属于Arduino的扩展板。

ECP 集成字段非必填配置

导读 INTRODUCTION 非必填设置&#xff1a;ECP主数据同步的时候&#xff0c;经常遇到一个问题&#xff0c;就是ECP报错&#xff0c;但是这个字段两边的ecp顾问与sf顾问都觉得没实际意思&#xff0c;觉得没有传输的必要性&#xff0c;这个时候我们就可以考虑非必输的字段不必输…

Linux命令--04----文件目录类命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 文件目录类命令pwdls 、llcdmkdirrmdirtouchcprmmv 查看文件catmorelessheadtail 打印信息echo\> 覆盖和>>追加lnhistory 文件目录类命令 pwd ls 、ll cd…

C--编译和链接见解

欢迎各位看官&#xff01;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论 感谢各位看官的支持&#xff01;&#xff01;&#xff01; 一&#xff1a;翻译环境和运行环境 在ANSIIC的任何一种实现中&#xff0c;存在两个不同的环境1&#xff0c;…

动手学深度学习(李沐)PyTorch 第 7 章 现代卷积神经网络

7.1 深度卷积神经网络&#xff08;AlexNet&#xff09; 在计算机视觉中&#xff0c;直接将神经网络与其他机器学习方法进行比较也许不公平。这是因为&#xff0c;卷积神经网络的输入是由原始像素值或是经过简单预处理&#xff08;例如居中、缩放&#xff09;的像素值组成的。但…

【C++】——list的介绍和模拟实现

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

亚马逊云乱扣费,被不知不觉扣钱真的好气呀

之前申请了亚马逊云12个月免费&#xff0c;一直也没用&#xff0c;也没登录&#xff0c;今天突然看到银行扣费信息&#xff0c;扣了我21美元&#xff0c;已经扣了我几个月了&#xff0c;登录亚马逊后台&#xff0c;实例为0的情况下&#xff0c;每个月都能扣21美元&#xff0c;国…

【HTML+CSS】留言板plus实现全过程

创建一个具有动态留言的简约风格留言板 在本教程中&#xff0c;我们将学习如何创建一个简约风格的留言板&#xff0c;它具备动态留言显示和一些基本动画效果。这个留言板将使用HTML和CSS构建&#xff0c;最终实现一个既美观又实用的界面。 准备工作 首先&#xff0c;确保你的…

UNRAID使用rclone挂在alist网盘

UNRAID使用rclone挂在alist网盘 需求&#xff1a;考虑异地备份&#xff0c;将部分重要的资料上传至网盘&#xff0c;保证nas中的资料安全。 考虑&#xff1a;当然网盘备份存在安全性问题&#xff0c;后续也可以通过加密的方式进行上传&#xff0c;不过这是后话&#xff0c;有精…

Python常见问题解答:从基础到进阶

Python常见问题解答&#xff1a;从基础到进阶 Python 是一种简单易学、功能强大的编程语言&#xff0c;广泛应用于数据分析、Web 开发、自动化脚本、人工智能等领域。即便如此&#xff0c;Python 开发者在编写代码的过程中&#xff0c;常常会遇到各种各样的问题。本文将从基础…

java集合 -- 面试

Java集合框架体系 ArrayList底层实现是数组 LinkedList底层实现是双向链表 HashMap的底层实现使用了众多数据结构&#xff0c;包含了数组、链表、散列表、红黑树等 List ps : 数据结构 -- 数组 ArrayList源码分析 ArrayList底层的实现原理是什么? ArrayList list new…

HKMG工艺为什么要用金属栅极?

知识星球里的学员问&#xff1a;在HKMG工艺中&#xff0c;会用到HfO2等作为栅介质层&#xff0c;为什么不能再用多晶硅做栅极&#xff0c;而是改为金属栅极&#xff1f; 什么是HKMG工艺&#xff1f; HKMG&#xff08;High-K Metal Gate &#xff09;&#xff0c;是45nm&#…