美文网首页
Compose Multiplatform初尝

Compose Multiplatform初尝

作者: h2coder | 来源:发表于2023-07-19 06:37 被阅读0次

    前言

    • Compose Multiplatform,让Compose不止用于Android开发,最近支持iOS也到了Alpha测试阶段
    • Compose和Flutter比较类似,都是声明式UI,所以上手其中一个,另外一个也很快上手了
    • 本篇用Compose写一个接口请求,然后使用列表渲染出来

    效果展示

    Compose.png

    依赖以及配置

    • shared模块的build.gradle.kts
    plugins {
        kotlin("multiplatform")
        id("com.android.library")
        id("kotlinx-serialization")
    }
    
    @OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
    kotlin {
        targetHierarchy.default()
    
        android {
            compilations.all {
                kotlinOptions {
                    jvmTarget = "1.8"
                }
            }
        }
    
        listOf(
            iosX64(),
            iosArm64(),
            iosSimulatorArm64()
        ).forEach {
            it.binaries.framework {
                baseName = "shared"
            }
        }
    
        sourceSets {
            val commonMain by getting {
                dependencies {
                    //put your multiplatform dependencies here
                    val ktorVersion = "2.0.1";
                    api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
                    //core
                    api("io.ktor:ktor-client-core:$ktorVersion")
                    //CIO
                    api("io.ktor:ktor-client-cio:$ktorVersion")
                    //Logging
                    api("io.ktor:ktor-client-logging:$ktorVersion")
                    api("io.ktor:ktor-client-content-negotiation:$ktorVersion")
                    //Json格式化
                    api("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
                    //时间格式化
                    api("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
                }
            }
    
            val commonTest by getting {
                dependencies {
                    implementation(kotlin("test"))
                }
            }
        }
    }
    
    android {
        namespace = "com.zh.kmmsample"
        compileSdk = 33
        defaultConfig {
            minSdk = 21
        }
    }
    
    • androidApp模块
    plugins {
        id("com.android.application")
        kotlin("android")
        id("kotlinx-serialization")
    }
    
    android {
        namespace = "com.zh.kmmsample.android"
        compileSdk = 33
    
        defaultConfig {
            applicationId = "com.zh.kmmsample.android"
            minSdk = 21
            targetSdk = 33
            versionCode = 1
            versionName = "1.0"
    
            multiDexEnabled = true
        }
    
        buildFeatures {
            compose = true
        }
    
        composeOptions {
            kotlinCompilerExtensionVersion = "1.4.7"
        }
    
        packaging {
            resources {
                excludes += "/META-INF/{AL2.0,LGPL2.1}"
            }
        }
    
        signingConfigs {
            getByName("debug") {
                storeFile = file("../key")
                storePassword = "123456"
                keyAlias = "key"
                keyPassword = "123456"
            }
    
            create("release") {
                storeFile = file("../key")
                storePassword = "123456"
                keyAlias = "key"
                keyPassword = "123456"
            }
        }
    
        buildTypes {
            getByName("debug") {
                isMinifyEnabled = false
                isDebuggable = true
            }
    
            getByName("release") {
                signingConfig = signingConfigs.getByName("release")
                isMinifyEnabled = false
                isDebuggable = false
                proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
            }
        }
    
        compileOptions {
            //由于Ktor中,有用到Java高版本的语法,所以需要增加解糖处理
            isCoreLibraryDesugaringEnabled = true
            sourceCompatibility = JavaVersion.VERSION_1_8
            targetCompatibility = JavaVersion.VERSION_1_8
        }
    
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }
    
    dependencies {
        api(project(":shared"))
        api("androidx.compose.ui:ui:1.4.3")
        api("androidx.compose.ui:ui-tooling:1.4.3")
        api("androidx.compose.ui:ui-tooling-preview:1.4.3")
        api("androidx.compose.foundation:foundation:1.4.3")
        api("androidx.compose.material:material:1.4.3")
        api("androidx.activity:activity-compose:1.7.1")
    
        //MultiDex
        api("androidx.multidex:multidex:2.0.1")
    
        val ktorVersion = "2.0.1";
        api("io.ktor:ktor-client-android:$ktorVersion")
    
        //由于Ktor中,有用到Java高版本的语法,所以需要增加解糖处理
        coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
    }
    

    接口API地址配置(Api.kt)

    object Api {
        /**
         * 问答
         */
        const val wenDaApi = "https://wanandroid.com/wenda/list/1/json"
    }
    

    实体类(WenDaModel.kt)

    /**
     * 问答,每日一问
     */
    @Serializable
    data class WenDaModel(
        val data: Data? = null,
        val errorCode: Int = 0,
        val errorMsg: String? = null
    ) {
        @Serializable
        data class Data(
            var curPage: Int = 0,
            var datas: List<Datas>? = null,
            var offset: Int = 0,
            var over: Boolean = false,
            var pageCount: Int = 0,
            var size: Int = 0,
            var total: Int = 0
        ) {
            @Serializable
            data class Datas(
                var adminAdd: Boolean = false,
                var apkLink: String? = null,
                var audit: Int = 0,
                var author: String? = null,
                var canEdit: Boolean = false,
                var chapterId: Int = 0,
                var chapterName: String? = null,
                var collect: Boolean = false,
                var courseId: Int = 0,
                var desc: String? = null,
                var descMd: String? = null,
                var envelopePic: String? = null,
                var fresh: Boolean = false,
                var host: String? = null,
                var id: Int = 0,
                //var isAdminAdd: Boolean = false,
                var link: String? = null,
                var niceDate: String? = null,
                var niceShareDate: String? = null,
                var origin: String? = null,
                var prefix: String? = null,
                var projectLink: String? = null,
                var publishTime: String = "0",
                var realSuperChapterId: Int = 0,
                var selfVisible: Int = 0,
                var shareDate: String = "0",
                var shareUser: String? = null,
                var superChapterId: Int = 0,
                var superChapterName: String? = null,
                var tags: List<Tags>? = null,
                var title: String? = null,
                var type: Int = 0,
                var userId: Int = 0,
                var visible: Int = 0,
                var zan: Int = 0,
            ) {
                @Serializable
                data class Tags(
                    val name: String? = null,
                    val url: String? = null
                )
            }
        }
    }
    

    HTTP请求工具类(HttpUtil.kt)

    /**
     * Http工具类
     */
    object HttpUtil {
        /**
         * 超时时间
         */
        private const val mTimeOutSeconds: Long = 15000
    
        /**
         * Http客户端
         */
    //    val mClient: HttpClient = HttpClient(CIO) {
        val mClient: HttpClient = HttpClient() {
            expectSuccess = true
    
    //        engine {
    //            maxConnectionsCount = 1000
    //            requestTimeout = mTimeOutSeconds
    //
    //            endpoint {
    //                maxConnectionsPerRoute = 100
    //                pipelineMaxSize = 20
    //                keepAliveTime = mTimeOutSeconds
    //                connectTimeout = mTimeOutSeconds
    //            }
    //        }
    
            install(Logging) {
                logger = Logger.DEFAULT
                level = LogLevel.HEADERS
            }
    
            install(ContentNegotiation) {
                json(Json {
                    isLenient = true
                    //如果Json有字段在实体类中找不到,那么忽略它,没有该配置的话,就会报错
                    ignoreUnknownKeys = true
                    prettyPrint = true
                    isLenient = true
                })
            }
        }
    
        /**
         * 发起GET请求
         */
        suspend inline fun <reified T> get(
            url: String
        ): T {
            return withContext(Dispatchers.IO) {
                val response = mClient.get(url = Url(url))
                response.call.body()
            }
        }
    
        /**
         * 发起POST请求
         */
        suspend inline fun <reified T> post(
            url: String
        ): T {
            return withContext(Dispatchers.IO) {
                val response = mClient.post(url = Url(url))
                response.call.body()
            }
        }
    }
    

    Application(App.kt)

    class App : Application() {
        override fun attachBaseContext(context: Context) {
            super.attachBaseContext(context)
            MultiDex.install(this)
        }
    }
    

    主题(MyApplicationTheme.kt)

    @Composable
    fun MyApplicationTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        content: @Composable () -> Unit
    ) {
        val colors = if (darkTheme) {
            darkColors(
                primary = Color(0xFFBB86FC),
                primaryVariant = Color(0xFF3700B3),
                secondary = Color(0xFF03DAC5)
            )
        } else {
            lightColors(
                primary = Color(0xFF6200EE),
                primaryVariant = Color(0xFF3700B3),
                secondary = Color(0xFF03DAC5)
            )
        }
        val typography = Typography(
            body1 = TextStyle(
                fontFamily = FontFamily.Default,
                fontWeight = FontWeight.Normal,
                fontSize = 16.sp
            )
        )
        val shapes = Shapes(
            small = RoundedCornerShape(4.dp),
            medium = RoundedCornerShape(4.dp),
            large = RoundedCornerShape(0.dp)
        )
    
        MaterialTheme(
            colors = colors,
            typography = typography,
            shapes = shapes,
            content = content
        )
    }
    

    列表条目(WenDaItem.kt)

    /**
     * 问答条目
     */
    @Composable
    fun WenDaItem(data: WenDaModel.Data.Datas?) {
        Card(
            modifier = Modifier
                .background(Color.White)
                .padding(10.dp)
                .fillMaxWidth(),
            elevation = 10.dp
        ) {
            Column(modifier = Modifier.padding(10.dp)) {
                Text(
                    text = "作者${data?.author}"
                )
                Text(text = "${data?.title}")
            }
        }
    }
    

    列表页面(MainActivity.kt)

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyApplicationTheme {
                    Surface(
                        modifier = Modifier.fillMaxSize(),
                        color = MaterialTheme.colors.background
                    ) {
                        ContentView()
                    }
                }
            }
        }
    }
    
    @Composable
    fun ContentView() {
        Column {
            val scope = rememberCoroutineScope()
            var wenDaModel by remember {
                mutableStateOf(WenDaModel())
            }
    
            //按钮
            Button(
                modifier = Modifier
                    .padding(10.dp)
                    .fillMaxWidth(),
                onClick = {
                    scope.launch {
                        wenDaModel = HttpUtil.get(
                            url = Api.wenDaApi
                        )
                    }
                }) {
                Text(text = "请求数据")
            }
    
            //列表
            LazyColumn {
                repeat(
                    wenDaModel.data?.datas?.size ?: 0
                ) {
                    item {
                        WenDaItem(data = wenDaModel.data?.datas?.get(it))
                    }
                }
            }
        }
    }
    
    @Preview
    @Composable
    fun DefaultPreview() {
        MyApplicationTheme {
            ContentView()
        }
    }
    

    清单配置(AndroidManifest.xml)

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
        <uses-permission android:name="android.permission.INTERNET" />
    
        <application
            android:name=".App"
            android:allowBackup="false"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    </manifest>
    

    相关文章

      网友评论

          本文标题:Compose Multiplatform初尝

          本文链接:https://www.haomeiwen.com/subject/uvvnudtx.html