推荐你一个基于Koin, Ktor Paging等组件的KMM Compose Multiplatform项目

news2024/11/26 2:30:23

推荐你一个基于Koin, Ktor & Paging等组件的KMM Compose Multiplatform项目

Kotlin Multiplatform Mobile(KMM)已经从一个雄心勃勃的想法发展成为一个稳定而强大的框架,为开发人员提供了在多个平台上无缝共享代码的能力。通过最近的稳定版本里程碑,KMM已成为跨平台开发领域的改变者。

环境设置

带有Kotlin Multiplatform插件的Android Studio

https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile

Kotlin版本:1.9.10
Gradle版本:8.1.1
用于演示的任何开放API - 在这里,我使用Internshala列表API来填充UI。
注意:使用Internshala的开放列表API,并没有使用不当的做法:)

设置Koin和Ktor

  1. 将两个依赖项添加到:shared模块中,我使用gradlelibrary目录作为依赖项。
    io.insert-koin:koin-core:3.5.0
  2. 对于Ktor,根据您的API,有多个依赖项各自具有自己的目的。
  3. 由于注入将在平台级别进行,因此我们需要将初始化部分暴露给两个平台。为此,我们将创建一个带有initKoin()方法的Helper类,并从iOS和Android的应用程序类调用此方法。
  4. initKoin() → 我们在这里定义我们稍后需要的所有依赖项。
//shared/src/commonMain/kotlin/di/Koin.kt
fun initKoin() = startKoin {
    modules(networkModule)
}
private val networkModule = module {
    single {
        HttpClient {
            defaultRequest {
                url.takeFrom(URLBuilder().takeFrom("https://internshala.com/"))
            }
            install(HttpTimeout) {
                requestTimeoutMillis = 15_000
            }
            install(ContentNegotiation) {
                json(Json {
                    ignoreUnknownKeys = true
                    prettyPrint = true
                })
            }
            install(Logging) {
                level = LogLevel.ALL
                logger = object : Logger {
                    override fun log(message: String) {
                        println(message)
                    }
                }
            }
        }
    }
}
  1. 从两个平台调用此方法
  • iOS:我们需要在2个不同的文件中进行更改。(共享iOS文件和特定于平台的文件)
// :shared/src/iosMain/kotlin/Koin.ios.kt
class Koin {
    fun initKoin() {
        di.initKoin()
    }
}
// iosApp/iosApp/iOSApp.swift
import SwiftUI
import shared
@main
struct iOSApp: App {
 var body: some Scene {
  WindowGroup {
   ContentView()
  }
 }

  init() {
       KoinKt.doInitKoin()
   }

  • Android:与iOS不同,对于Android,我们可以直接调用:shared initKoin()方法并初始化Koin。
class App : Application() {
    override fun onCreate() {
        super.onCreate()
        initKoin()
    }
}

将API响应与Result类进行映射

我们将在Ktor的HttpClient上编写一个小的扩展,用于执行所有API调用并将响应包装在Result(成功/错误)中,并附带适当的消息。

suspend inline fun <reified T> HttpClient.fetch(
    block: HttpRequestBuilder.() -> Unit
): Result<T> = try {
    val response = request(block)
    if (response.status == HttpStatusCode.OK)
        Result.Success(response.body())
    else
        Result.Error(Throwable("${response.status}: ${response.bodyAsText()}"))
} catch (e: Exception) {
    Result.Error(e)
}

sealed interface Result<out R> {
    class Success<out R>(val value: R) : Result<R>
    data object Loading : Result<Nothing>
    class Error(val throwable: Throwable) : Result<Nothing>
}

设置分页库

在实施Koin和Ktor之后,接下来是分页库。我们将使用来自cashapp的开源库。
由于Paging提供了多种方法来处理错误和API响应,我们将创建一个组合,以处理这种行为。

@Composable
fun <T : Any> PagingListUI(
    data: LazyPagingItems<T>,
    content: @Composable (T) -> Unit
) {
    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {

        items(data.itemCount) { index ->
            val item = data[index]
            item?.let { content(it) }
            Divider(
                color = UiColor.background,
                thickness = 10.dp,
                modifier = Modifier.border(border = BorderStroke(0.5.dp, Color.LightGray))
            )
        }

        data.loadState.apply {
            when {
                refresh is LoadStateNotLoading && data.itemCount < 1 -> {
                    item {
                        Box(
                            modifier = Modifier.fillParentMaxSize(),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(
                                text = "No Items",
                                modifier = Modifier.align(Alignment.Center),
                                textAlign = TextAlign.Center
                            )
                        }
                    }
                }

                refresh is LoadStateLoading -> {
                    item {
                        Box(
                            modifier = Modifier.fillParentMaxSize(),
                            contentAlignment = Alignment.Center
                        ) {
                            CircularProgressIndicator(
                                color = UiColor.primary
                            )
                        }
                    }
                }

                append is LoadStateLoading -> {
                    item {
                        CircularProgressIndicator(
                            color = UiColor.primary,
                            modifier = Modifier.fillMaxWidth()
                                .padding(16.dp)
                                .wrapContentWidth(Alignment.CenterHorizontally)
                        )
                    }
                }

                refresh is LoadStateError -> {
                    item {
                        ErrorView(
                            message = "No Internet Connection.",
                            onClickRetry = { data.retry() },
                            modifier = Modifier.fillParentMaxSize()
                        )
                    }
                }

                append is LoadStateError -> {
                    item {
                        ErrorItem(
                            message = "No Internet Connection",
                            onClickRetry = { data.retry() },
                        )
                    }
                }
            }
        }
    }
}

@Composable
private fun ErrorItem(
    message: String,
    modifier: Modifier = Modifier,
    onClickRetry: () -> Unit
) {
    Row(
        modifier = modifier.padding(16.dp),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text(
            text = message,
            maxLines = 1,
            modifier = Modifier.weight(1f),
            color = androidx.compose.ui.graphics.Color.Red
        )
        OutlinedButton(onClick = onClickRetry) {
            Text(text = "Try again")
        }
    }
}

@Composable
private fun ErrorView(
    message: String,
    modifier: Modifier = Modifier,
    onClickRetry: () -> Unit
) {
    Column(
        modifier = modifier.padding(16.dp).onPlaced { _ ->
        },
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = message,
            maxLines = 1,
            modifier = Modifier.align(Alignment.CenterHorizontally),
            color = androidx.compose.ui.graphics.Color.Red
        )
        OutlinedButton(
            onClick = onClickRetry, modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
                .wrapContentWidth(Alignment.CenterHorizontally)
        ) {
            Text(text = "Try again")
        }
    }
}

设置App和Internship屏幕的UI部分

我们的应用程序入口点 - App.kt

// :shared/src/commonMain/kotlin/App.kt
@Composable
fun App() {
    MaterialTheme {
        val screens = Screen.values()
        var selectedScreen by remember { mutableStateOf(screens.first()) }

        Scaffold(
            bottomBar = {
                BottomNavigation(
                    backgroundColor = Color.White,
                    modifier = Modifier.height(64.dp)
                ) {
                    screens.forEach { screen ->
                        BottomNavigationItem(
                            modifier = Modifier.background(Color.White),
                            selectedContentColor = ui.theme.Color.textOnPrimary,
                            unselectedContentColor = Color.Gray,
                            icon = {
                                Icon(
                                    imageVector = getIconForScreen(screen),
                                    contentDescription = screen.textValue
                                )
                            },
                            label = { Text(screen.textValue) },
                            selected = screen == selectedScreen,
                            onClick = { selectedScreen = screen },
                        )
                    }
                }
            },
            content = { getScreen(selectedScreen) }
        )
    }
}

@Composable
fun getIconForScreen(screen: Screen): ImageVector {
    return when (screen) {
        Screen.INTERNSHIPS -> Icons.Default.AccountBox
        Screen.JOBS -> Icons.Default.Add
        Screen.COURSES -> Icons.Default.Notifications
        else -> Icons.Default.Home
    }
}

@Composable
fun getScreen(selectedScreen: Screen) = when (selectedScreen) {
    Screen.INTERNSHIPS -> InternshipsScreen().content()
    Screen.JOBS -> JobsScreen()
    Screen.COURSES -> CoursesScreen()
    else -> HomeScreen()
}

获取分页数据的实习界面

class InternshipsScreen : KoinComponent {
    private val viewModel: InternshipViewModel by inject()

    @Composable
    fun content() {
        val result by rememberUpdatedState(viewModel.internships.collectAsLazyPagingItems())
        return Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text("Internships") },
                    elevation = 0.dp,
                    navigationIcon = {
                        IconButton(onClick = { println("Drawer clicked") }) {
                            Icon(imageVector = Icons.Default.Menu, contentDescription = "Menu")
                        }
                    },
                    actions = {
                        IconButton(onClick = { println("Search Internships!") }) {
                            Icon(imageVector = Icons.Default.Search, contentDescription = "Search")
                        }
                    },
                    backgroundColor = Color.White
                )
            },
            drawerContent = { /*Drawer content*/ },
            content = { PagingListUI(data = result, content = { InternshipCard(it) }) },
        )
    }
}

输出的预览

GitHub

https://github.com/Prashant-123/kmm-ktor-koin
https://github.com/Kamel-Media/Kamel

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

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

相关文章

2023.11.25电商项目平台建设2 -四大业务之核销主题建模

1.数仓建模步骤 自下而上 ADS-DWS-DWM-DWD 2.DWD方案(清洗转换,降维拉宽) DWD层的表 dwd_sale_store_sale_dtl_i 门店销售明细宽表 维度dim 销售sale合成成的宽表 dwd_dim_date_f 日期表 store_sale_dtl 门店销售明细表 dwd_sale_store_sale_dtl_i 门店销售明细表 …

漏洞分析 | 经典的Shiro反序列化

0x01、前言 相信大家总是面试会问到java反序列化&#xff0c;或者会问到标志性的漏洞&#xff0c;比如shiro反序列化&#xff0c;或者weblogic反序列化漏洞。 那我就这篇文章为大家讲解一下&#xff0c;不懂的哥哥直接背一下&#xff0c;理解一下就好了。 至于为什么要选择sh…

力扣(LeetCode)907. 子数组的最小值之和(C++)

枚举 请对题目有疑惑的小伙伴看枚举思想&#xff0c;有助于掌握最基本的解题思路。对于本题数据范围&#xff0c;枚举算法会超时。 请看题目描述&#xff1a;给定一个整数数组 arr&#xff0c;找到 min(b) 的总和&#xff0c;其中 b 的范围为 arr 的每个&#xff08;连续&…

锂电池污水如何处理

锂电池是目前应用广泛的重要电池类型&#xff0c;然而其生产过程和废弃处理中产生的污水对环境造成了不可忽视的影响。本文将探讨锂电池污水的处理方法&#xff0c;以期为环境保护和可持续发展作出贡献。 首先&#xff0c;了解锂电池污水的组成是解决问题的关键。锂电池污水通…

解释LED显示屏的裸眼3D特效原理

LED电子大屏幕的3D特效技术正在不断发展&#xff0c;而实现这一技术的原理主要包括分光、分色、分时和光栅等四种方法。这些原理都有各自的特点和应用场景&#xff0c;下面将对它们进行详细介绍。 1. 分光方法 分光方法是一种基于偏振光的3D显示技术。通过使用偏振滤镜或偏振片…

webshell之编码免杀

Unicode编码 jsp支持unicode编码&#xff0c;如果杀软不支持unicode查杀的话&#xff0c;基本上都能绕过 注意这里的\uuuu00可以换成\uuuu00uuu...可以跟多个u达到绕过的效果 将代码&#xff08;除page以及标签&#xff09;进行unicode编码&#xff0c;并条件到<%%>标签…

Drupal Core 8 PECL YAML 反序列化任意代码执行漏洞(CVE-2017-6920)

漏洞描述 影响软件&#xff1a;Drupal方式&#xff1a;反序列化参考链接&#xff1a;CVE-2017-6920:Drupal远程代码执行漏洞分析及POC构造效果&#xff1a;任意代码执行 漏洞环境及利用 搭建docker环境 环境启动后&#xff0c;访问 将会看到drupal的安装页面&#xff0c;一路…

仅2万粉,带了2.6万件的货!TikTok Shop美区达人周榜(11.13-11.19)

11月24日&#xff0c;TikTok Shop近日公布了美国市场和英国市场的全托管黑五大促战绩。数据显示&#xff0c;11月14日至11月20日&#xff0c;其美国市场的订单量环比10月20日-10月26日增长了205%。 家居户外热销品有&#xff1a;数码触摸屏相框、毛绒地毯、家居毛毯。黑马商品…

Callable、Future和FutrueTask详解

一、Callable介绍 1.1 Runnable介绍 Runnable是一个接口&#xff0c;里面声明了run方法。但是由于run方法返回值类型为void&#xff0c;所以在执行完成任务后&#xff0c;无法返回任何结果。 FunctionalInterface public interface Runnable {public abstract void run(); }…

手机爬虫用Fiddler详细教程

如果你正在进行手机爬虫的工作&#xff0c;那么一款强大而又实用的网络调试工具Fiddler将会是你的好帮手。今天&#xff0c;我将和大家分享一份详细的Fiddler教程&#xff0c;教你如何使用它来轻松捕获和分析手机App的网络请求。让我们一起来探索Fiddler的功能和操作&#xff0…

内衣洗衣机怎么选?内衣洗衣机便宜好用的牌子推荐

相信不少用户并不太在意衣服和内衣裤裤能不能同时洗&#xff0c;每次清洗都是把内衣裤与其他衣服一起放入洗衣机清洗&#xff0c;其实内衣裤不能直接跟大件的衣物一起放入洗衣机洗的&#xff0c;很容易会造成我们皮肤的瘙痒&#xff0c;我们大部分时间都在户外&#xff0c;暴露…

2023年11月27日历史上的今天大事件早读

1852年11月27日 计算机程序创始人阿达-洛芙莱斯去世 1893年11月27日 抗日爱国将领续范亭诞辰 1895年11月27日 《茶花女》作者、法国著名作家小仲马逝世 1899年11月27日 董其武将军诞辰 1902年11月27日 《新小说》创刊 1907年11月27日 割让刚果给比利时的条约签订 1925年1…

AMD ROCm软件栈组件介绍

AMD ROCm™ Platform 1.1 ROCm简介 参考&#xff1a;https://github.com/RadeonOpenCompute/ROCm ROCm&#xff08;Radeon Open Compute&#xff09;开源软件栈。 在NVIDIA GPU上&#xff0c;术语“CUDA”通常是指GPU编程编译器、API和运行时库&#xff0c;但ROCm不那么单一…

C语言基础篇5:指针(一)

指针是C语言的核心、精髓所在&#xff0c;用好了指针可以在C语言编程中起到事半功倍的效果。指针一方面可以提高程序的编译效率和执行速度&#xff0c;而且还可以通过指针实现动态的存储分配&#xff0c;另一方面使用指针可使程序更灵活&#xff0c;便于表示各种数据结构&#…

数据结构与算法编程题26

计算二叉树深度 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std;typedef char ElemType; #define ERROR 0 #define OK 1 #define Maxsize 100 #define STR_SIZE 1024typedef struct BiTNode {ElemType data;BiTNode* lchild, * rchild; }BiTNo…

Python基础:字符串(String)详解(需补充完善)

1. 字符串定义 在Python中&#xff0c;字符串是一种数据类型&#xff0c;用于表示文本数据。字符串是由字符组成的序列&#xff0c;可以包含字母、数字、符号和空格等字符。在Python中&#xff0c;你可以使用单引号&#xff08;&#xff09;或双引号&#xff08;"&#xf…

在 Banana Pi BPI-R2 PRO RK3568开源路由器上安装 OpenWrt 23 快照固件

这是在 BPI-R2 Pro&#xff08;到内部 eMMC&#xff09;上安装 OpenWrt 23 快照固件的快速指南。该固件已预装 LuCI 和一些软件包。这是 2023 年 9 月 2 日的屏幕截图。 LuCI 主页概述。Linux内核是6.1.50 网络接口概述。PPPoE 连接已启动并正在运行 速度测试和 CPU 使用情况…

谈谈中间件设计的思路

前言 想要设计和真正理解中间件的架构理论和思想。对于开发来说需要具备三个关键的能力 1&#xff1a;基础通用技术的深入理解和运用2&#xff1a;了解和熟悉常见中间件的设计思想&#xff0c;且有自己的感悟,并且能按照自己的理解模仿写一写3&#xff1a;业务的高度理解能力…

赞比亚市场开发攻略,带你走进非洲“铜矿王国”

赞比亚是非洲最早跟中国建交的国家&#xff0c;跟我们经贸联系一直也比较紧密。赞比亚很多生产生活资料比较依赖进口&#xff0c;市场潜力还是不错的。今天就来给大家分享一下非洲这个铜矿王国的开发攻略。文章略长&#xff0c;大家点赞收藏关注慢慢看。 文章目录&#xff1a;…