如何在 Kotlin Multiplatform 库的 API 中避免请求 Android Context

如何在 Kotlin Multiplatform 库的 API 中避免请求 Android Context

假设你正在进行 Kotlin Multiplatform 项目的开发。
你需要从通用代码中获取用户的 GPS 位置,并且目前没有现成的库可以实现该功能。
这时,你决定编写一个新的 Kotlin Multiplatform 库,以在 Android 和 iOS 上抽象 GPS 定位功能,因为你正在开发一个移动应用。
由于想不出好名字,你就给它起名为 KLocationManager
然后,你开始规划库所暴露的公共 API。可能是这样的:

private val kLocation: KLocationManager = KLocationManager()

    fun showLocationUpdates() {
        scope.launch {
            kLocation.observeLocation().collect {
                showMessage("Got (${it.lat}, ${it.lng})")
            }
        }
    }

接着,你开始研究如何在这两个移动平台上实际获取 GPS 位置流。

iOS 实现

在 iOS 上,一切看起来都很简单。有一个名为 CLLocationManager 的类,你可以进行初始化,并附加一个委托(用于接收更新的回调),然后调用 startUpdatingLocation() 方法即可。

val locationManager = CLLocationManager()

fun observeLocation() = callbackFlow<KLocation> {
    var mDelegate: CLLocationManagerDelegateProtocol = object : CLLocationManagerDelegateProtocol, NSObject() {
        override fun locationManager(
            manager: CLLocationManager,
            didUpdateLocations: List<*>
        ) {
            val locations = didUpdateLocations.map { it as CLLocation }
            if (locations.isNotEmpty()) {
                locations.last().coordinate.useContents { 
                    trySend(this.toKLocation())
                }
            }
        }
    }

    locationManager.apply {
        delegate = mDelegate
        startUpdatingLocation()
    }

    awaitClose {
        locationManager.stopUpdatingLocation()
    }
}

Android 实现

在 Android 上,你想使用 FusedLocationProviderClient,它对于这个用例来说足够简单。但是你会注意到从第一行代码开始就有问题:

val fusedLocationClient =
    LocationServices.getFusedLocationProviderClient(context)

Context 的问题

正如你可能已经猜到的,我们正在编写一个多平台库,而我们已经需要一个非常依赖于平台的东西:Android Context。
你可以要求 Android 用户在使用库之前通过调用 init 方法来传递一些 Context:

KLocationManager.init(context)

然而,当在另一个多平台项目中使用该库时,这将增加一些复杂性,因为用户将需要添加特定于平台的代码来调用 init 方法,但这只适用于 Android。
在 StackOverflow 上,还有其他一些不太正规的解决方案,比如为每个平台创建不同的构造函数、创建一个通用的 Context 类(在 iOS 上无操作,在 Android 上是一个实际的 Context)、使用 DI 等等…
但是,有没有一种解决方案可以在库的内部处理所有这些问题,并且不需要用户付出努力,以便他们可以在两个平台和通用代码上使用完全相同的 API 呢?

Jetpack App Startup 解决方案

App Startup 是 Jetpack 中的一个库,用于在应用启动时初始化库的组件(从名称上就可以猜到)。

https://developer.android.com/topic/libraries/app-startup

对我们来说,这可能很有用,因为:

  • 它会在任何库代码之前自动运行初始化步骤(因此我们确保我们的组件将始终在使用前被初始化);
  • 它仅适用于 Android,不需要在 iOS 模块中进行任何修改;
  • 它提供了在初始化阶段获取 Android Context 的方法。

正是因为最后一个原因,这对于我们来说可能很有用,因为我们正在寻找一种在不打扰用户的情况下向库代码注入 Android Context 的方法。
由于 App Startup 只需要在 Android 模块中使用,我们可以将其定义为 androidMain 的依赖项:

val androidMain by getting {
    dependencies {
        implementation("androidx.startup:startup-runtime:lastVersion")
        [...]
    }
}

可以在此处(https://developer.android.com/topic/libraries/app-startup)查看该库的最新发布版本。

AppStartup 将在启动时调用一个实现了 Initializer 接口的特殊类。

public interface Initializer<T> {

    /**
     * Initializes and a component given the application {@link Context}
     *
     * @param context The application context.
     */
    @NonNull
    T create(@NonNull Context context);

    /**
     * @return A list of dependencies that this {@link Initializer} depends on. This is
     * used to determine initialization order of {@link Initializer}s.
     * <br/>
     * For e.g. if a {@link Initializer} `B` defines another
     * {@link Initializer} `A` as its dependency, then `A` gets initialized before `B`.
     */
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}

第一个方法对我们来说非常有意义,请注意,通过实现此接口,我们可以在应用中免费获取一个 Context。
而第二个方法则在你的库依赖于其他库的初始化程序时有用,这样你就可以指定应用在启动时按照哪个顺序运行初始化程序。对于我们的用例,我们可以忽略这个方法。
因此,在我们的示例中,我们将从 create() 方法中获取 Context,并将其存储在我们可以随后访问的地方。

import android.content.Context
import androidx.startup.Initializer

internal lateinit var applicationContext: Context
    private set

public object KLocationContext

public class KLocationInitializer: Initializer<KLocationContext> {
    override fun create(context: Context): KLocationContext {
        applicationContext = context.applicationContext
        return KLocationContext
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

注意,KLocationInitializer.kt 将位于 android/platform-specific 模块中,而不是我们库的通用代码中。

最后,App Startup 使用一个特殊的内容提供程序来发现和调用所有组件的初始化程序。在 AndroidManifest.xml 中增加一个条目将使我们的 Initializer 可被发现。

<application>
    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">

        <meta-data  android:name="dev.paolorotolo.klocation.KLocationInitializer"
            android:value="androidx.startup" />
    </provider>
    [...]
</application>

我们可以放心地使用我们库的 AndoridManifest.xml,因为它最终将与使用它的应用的主 Manifest 合并。

到此为止,我们完成了!
每当我们在 Android 模块中需要一个 Context 时,我们实际上可以访问自动初始化的 applicationContext 变量。
现在,Android 的定位代码将会是这样的:

// injected context here from App Startup
val fusedLocationClient 
    = LocationServices.getFusedLocationProviderClient(applicationContext)

fun observeLocation(): Flow<KLocation> = callbackFlow {
    val locationCallback = object: LocationCallback(){
        override fun onLocationResult(locationResult: LocationResult) {
            super.onLocationResult(locationResult)
            trySend(locationResult)
        }
    }

    fusedLocationClient.requestLocationUpdates(
        buildLocationRequest(),
        locationCallback,
        Looper.getMainLooper()
    ).addOnFailureListener { close(it) }

    awaitClose {
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }
}.map { it.asKLocation() }

结论

我们使用 App Startup 创建的个人上下文注入器将具有以下特点:

  • 仅在 Android 上运行(因为 androidx-startup 是 androidMain 的依赖项);
  • 仅在 Android 特定代码模块中提供 applicationContext(因为它位于与 KLocationInitializer.kt 相同的模块中);
  • 对于库的用户来说是透明的,他们可以在通用代码和所有支持的目标中调用相同的 API observeLocation()

这样,我们就完成了如何在 Kotlin Multiplatform 库的 API 中避免请求 Android Context 的介绍。希望本文对你有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/348333.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构——静态链表

1.定义&#xff1a; &#xff08;1&#xff09;单链表&#xff1a;各个结点散落在内存中的各个角落&#xff0c;每个结点有指向下一个节点的指针(下一个结点在内存 中的地址); &#xff08;2&#xff09;静态链表&#xff1a;用数组的方式来描述线性表的链式存储结构: 分配一…

Windows中Zookeeper与kafka的安装配置

一、Zookeeper安装与使用 1.安装包下载 直接在官网下载即可Apache ZooKeeper。 下载后直接解压到本地即可。 2.环境配置 1> 在目录中下增加data和log文件夹 2> 解压目录下的 conf 目录&#xff0c;将目录中的 zoo_sample.cfg 文件&#xff0c;复制一份&#xff0c;重…

STC89C51单片机

本文为博主 日月同辉&#xff0c;与我共生&#xff0c;csdn原创首发。希望看完后能对你有所帮助&#xff0c;不足之处请指正&#xff01;一起交流学习&#xff0c;共同进步&#xff01; > 发布人&#xff1a;日月同辉,与我共生_单片机-CSDN博客 > 欢迎你为独创博主日月同…

【pytorch】pytorch学习笔记(续1)

p22&#xff1a;1.加减乘除&#xff1a; &#xff08;1&#xff09;add(a,b)&#xff1a;等同于ab。 &#xff08;2&#xff09;sub(a,b)&#xff1a;等同于a-b。 &#xff08;3&#xff09;mul(a,b)&#xff1a;等同于a*b。 &#xff08;4&#xff09;div(a,b)&#xff1a…

低成本扫码点餐:1000元全包

在数字化时代&#xff0c;扫码点餐已经成为餐饮行业的标配。然而&#xff0c;对于许多小规模或初创的餐饮企业来说&#xff0c;开发一套完整的扫码点餐系统是一项成本高昂的任务。今天&#xff0c;我们将向您介绍一个低成本、高效的方法&#xff0c;让您用1000块钱轻松搞定一套…

反光衣穿戴识别摄像机

反光衣穿戴识别摄像机是一种基于图像识别技术的智能设备&#xff0c;旨在识别和监测道路上穿戴反光衣的行人和工作者&#xff0c;以提高道路交通安全。 反光衣穿戴识别摄像机利用高清摄像头捕捉道路上的实时图像&#xff0c;并通过图像处理算法进行人体检测和识别&#xff0c;识…

Programming Abstractions in C阅读笔记:p248-p253

《Programming Abstractions in C》学习第69天&#xff0c;p248-p253总结&#xff0c;总计6页。 一、技术总结 “A generalized program for two-player games”如标题所示&#xff0c;该小节强调要学会从一个复杂的程序中抽象出通用的内容——这也是本书的主旨——“Program…

RocketMQ源码阅读-十-事务消息

RocketMQ源码阅读-十-事务消息 交互流程事务消息发送Producer发送事务消息Broker处理结束事务请求Broker 生成 ConsumeQueue 事务消息回查Broker发起回查Producer 接收回查 总结 交互流程 事务消息交互流程图如下&#xff1a;事务消息发送步骤如下&#xff1a; 生产者将半事务…

40元一碗的面,卖不动了?

一、在熙熙攘攘的商场中&#xff0c;两家门店“格格不入” 周五&#xff08;1月19日&#xff09;下午&#xff0c;人群从写字楼向购物中心转移。6点前后&#xff0c;北京合生汇商场的多个过道、上下行扶梯已经熙熙攘攘&#xff0c;B1、B2层的美食街区更是热闹。 一片喧哗中&…

GIS项目实战07:Eclipse资源分享

官网下载&#xff1a;Eclipse Downloads | The Eclipse Foundation 百度网盘分享&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1YBKw8k0a0DouSWZmDg8fYw 提取码&#xff1a;1234 &#xff08;链接失效请私信&#xff09; 无需安装&#xff0c;解压即可使用

小程序系列--12使用 npm 包

一、Vant Weapp 1. 什么是 Vant WeappVant Weapp 是有赞前端团队开源的一套小程序 UI 组件库&#xff0c;助力开发者快速搭建小程序应用。它所使用的是 MIT 开源许可协议&#xff0c;对商业使用比较友好。 官方文档地址 https://youzan.github.io/vant-weapp 2. 安装 Vant 组…

Power BI - 5分钟学习新增度量值

每天5分钟&#xff0c;今天介绍Power BI新增度量值 在 Power BI Desktop 中&#xff0c;你可以创建度量值。度量值用于计算表达式的结果。 在创建自己的度量值时&#xff0c;需要使用DAX语言。 DAX包括超过200个函数、运算符等&#xff0c;几乎可以计算任何数据分析所需的结果…

智能风控体系之divergence评分卡简介

评分卡模型的出现据说最早是在20世纪40年代&#xff0c;Household Finance and Spiegel和芝加哥邮购公司第一次尝试在贷款决策过程中使用信用评分.但是这两家公司都终止了这项业务。后来&#xff0c;在20世纪50年代末&#xff0c;伊利诺伊州的美国投资公司&#xff08;AIC&…

【C语言】素数的N种代码形式

The words written in front 大家好&#xff0c;我是xiaoxie,希望你看完之后对你能有所帮助&#xff0c;不足之处&#xff0c;请批评指正&#xff01; 希望可以和大家一起交流学习进步&#xff01; Introduction 大家都知道&#xff1a;“质数又称素数。一个大于1的自然数&a…

ECharts实现简单饼图和柱状图

1.JAVA 1.饼图 前端使用vue&#xff0c;后端使用SpringBoot <template><div><div class"card" style"padding: 15px">数据可视化分析&#xff1a;图书类型销量</div><div style"display: flex; margin: 10px 0"&g…

Github 2024-01-21 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-21统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目7Cuda项目1HTML项目1Jupyter Notebook项目1非开发语言项目1 高级英语学习指南 创建周期&#xff…

Oracle1 数据库管理

Oracle的安装 一、基础表的创建 1.1 切换到scott用户 用sys 账户 登录 解锁scott账户 alter user scott account unlock;conn scott/tiger;发现并不存在scott账户&#xff0c;自己创建一个&#xff1f; 查找资料后发现&#xff0c;scott用户的脚本需要自己执行一下 C:\ap…

【Fooocus 深度学习】SDXL,AIGC生图,源码解读

文章目录 使用通配符增加prompt多样性Fooocus的风格实现 使用通配符增加prompt多样性 prompt和negative_prompt都可以通过apply_wildcards函数来实现通配符替换&#xff0c;apply_wildcards会从txt中随机找一个出来。 promptsunshine, river, trees, __artist__ task_prompt …

Adobe Acrobat DC软件安装后右键菜单点击无反应报错解决办法

安装了Adobe Acrobat DC软件后&#xff0c;Adobe Acrobat DC软件会修改系统的右键菜单&#xff0c;如果一旦Adobe Acrobat DC软件出现问题&#xff0c;你的右键菜单也就不能使用了&#xff0c;出现未响应的状态。 那如何恢复系统原来的右键菜单呢&#xff0c;办法很简单&#x…

在线海报图片设计器、图片编辑器源码/仿照稿定设计源码

在线海报设计系统素材设计源码是一个漂亮且功能强大的在线海报图片设计器&#xff0c;仿照稿定设计而成。该系统适用于多种场景&#xff0c;包括海报图片生成、电商分享图、文章长图、视频/公众号封面等。用户无需下载软件&#xff0c;即可轻松实现创意&#xff0c;迅速完成排版…