Compose Multiplatform初尝

2023-07-19  本文已影响0人  h2coder

前言

效果展示

Compose.png

依赖以及配置

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
    }
}
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>
上一篇下一篇

猜你喜欢

热点阅读