Jetpack Compose 教學上課講義【從零開始學 Jetpack Compose 程式設計】Room

【從零開始學 Jetpack Compose 程式設計】

線上教學課程目錄: https://bit.ly/3JF4SFA
Youtube 課程播放清單:https://bit.ly/3tFjRbx
Udemy 線上課程:https://bit.ly/3MbVnhO


ViewModel: StudentViewModel
Repository: StudentRepository
Room Database: StudentRoomDatabase
Dao: StudentDao
Entities: Student

Gradle(App)

compose_version = '1.1.1'
room_version = '2.4.2'

build.gradle(module)

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    
    //編譯器註解處理工具(Kotlin annotation processing tool)
    id 'kotlin-kapt'
}

Gradle(Module)

dependencies {
    //空專案預設 dependencies
    implementation 'androidx.core:core-ktx:1.8.0'
    implementation "androidx.compose.ui:ui:$compose_version"
    implementation "androidx.compose.material:material:$compose_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
    implementation 'androidx.activity:activity-compose:1.4.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
    
    ...
    ...
    ...
    //新增加 dependencies
   //room
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    //livedata
    implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
    
    //coroutine
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'

Entities: Student

package com.thishkt.myapplication


import androidx.annotation.NonNull
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "students")
class Student {

    @PrimaryKey(autoGenerate = true)
    @NonNull
    @ColumnInfo(name = "id")
    var id: Int = 0

    @ColumnInfo(name = "studentName")
    var studentName: String = ""
    var score: Int = 0

    constructor() {}

    constructor(id: Int, studentname: String, score: Int) {
        this.studentName = studentname
        this.score = score
    }

    constructor(studentname: String, score: Int) {
        this.studentName = studentname
        this.score = score
    }
}

Dao: StudentDao

package com.thishkt.myapplication


import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update

@Dao
interface StudentDao {

    @Insert
    fun insertStudent(student: Student)

    @Query("UPDATE students SET score=:score WHERE studentName = :name")
    fun updateStudent(name: String, score: Int)

    @Query("SELECT * FROM students WHERE studentName = :name")
    fun findStudent(name: String): List<Student>

    @Query("DELETE FROM students WHERE studentName = :name")
    fun deleteStudent(name: String)

    @Query("SELECT * FROM students")
    fun getAllStudents(): LiveData<List<Student>>

}

Room Database: StudentRoomDatabase

package com.thishkt.myapplication


import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [(Student::class)], version = 1)
abstract class StudentRoomDatabase: RoomDatabase()  {

    abstract fun studentDao(): StudentDao

    companion object {

        private var INSTANCE: StudentRoomDatabase? = null

        fun getInstance(context: Context): StudentRoomDatabase {
            synchronized(this) {
                var instance = INSTANCE

                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        StudentRoomDatabase::class.java,
                        "student_database"
                    ).fallbackToDestructiveMigration()
                        .build()

                    INSTANCE = instance
                }
                return instance
            }
        }
    }
}

Repository: StudentRepository

package com.thishkt.myapplication


import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.*

class StudentRepository(private val studentDao: StudentDao) {

    val allStudents: LiveData<List<Student>> = studentDao.getAllStudents()
    val searchResults = MutableLiveData<List<Student>>()

    private val coroutineScope = CoroutineScope(Dispatchers.Main)

    fun insertStudent(newstudent: Student) {
        coroutineScope.launch(Dispatchers.IO) {
            studentDao.insertStudent(newstudent)
        }
    }

    fun deleteStudent(name: String) {
        coroutineScope.launch(Dispatchers.IO) {
            studentDao.deleteStudent(name)
        }
    }

    fun findStudent(name: String) {
        coroutineScope.launch(Dispatchers.Main) {
            searchResults.value = asyncFind(name).await()
        }
    }

    fun updateStudent(name: String, score: Int) {
        coroutineScope.launch(Dispatchers.IO) {
            studentDao.updateStudent(name, score)
        }
    }


    private fun asyncFind(name: String): Deferred<List<Student>?> =
        coroutineScope.async(Dispatchers.IO) {
            return@async studentDao.findStudent(name)
        }
}

ViewModel: StudentViewModel

package com.thishkt.myapplication


import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData

class StudentViewModel(application: Application) {

    val allStudents: LiveData<List<Student>>
    private val repository: StudentRepository
    val searchResults: MutableLiveData<List<Student>>

    init {
        val studentDb = StudentRoomDatabase.getInstance(application)
        val studentDao = studentDb.studentDao()
        repository = StudentRepository(studentDao)

        allStudents = repository.allStudents
        searchResults = repository.searchResults
    }

    fun insertStudent(student: Student) {
        repository.insertStudent(student)
    }

    fun findStudent(name: String) {
        repository.findStudent(name)
    }

    fun deleteStudent(name: String) {
        repository.deleteStudent(name)
    }


    fun updateStudent(name: String, score: Int) {
        repository.updateStudent(name,score)
    }
}

MainActivity

package com.thishkt.myapplication


import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import android.app.Application
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.selectable
import androidx.compose.ui.Alignment


class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Demo()
        }
    }
}

@Composable
fun Demo(
    viewModel: StudentViewModel =
        StudentViewModel(LocalContext.current.applicationContext as Application)
) {

    val allStudents by viewModel.allStudents.observeAsState(listOf())
    val searchResults by viewModel.searchResults.observeAsState(listOf())

    MainScreen(
        allStudents = allStudents,
        searchResults = searchResults,
        viewModel = viewModel

    )
}

@Composable
fun MainScreen(
    allStudents: List<Student>,
    searchResults: List<Student>,
    viewModel: StudentViewModel
) {
    var studentName by remember { mutableStateOf("") }
    var studentScore by remember { mutableStateOf("") }
    var searching by remember { mutableStateOf(false) }

    val onStudentTextChange = { text: String ->
        studentName = text
    }

    val onScoreTextChange = { text: String ->
        studentScore = text
    }

    Column(
        horizontalAlignment = CenterHorizontally,
        modifier = Modifier
            .fillMaxWidth()
    ) {
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Row(
                modifier = Modifier
                    .weight(0.5f)
            ) {
                CustomTextField(
                    title = "學生姓名",
                    textState = studentName,
                    onTextChange = onStudentTextChange,
                    keyboardType = KeyboardType.Text
                )
            }
            Row(
                modifier = Modifier
                    .weight(0.5f)
            ) {

                CustomTextField(
                    title = "學期成績",
                    textState = studentScore,
                    onTextChange = onScoreTextChange,
                    keyboardType = KeyboardType.Number
                )
            }

            Button(onClick = {
                searching = false
                studentName = ""
                studentScore = ""
            }) {
                Text("清除")
            }
        }

        Row(
            horizontalArrangement = Arrangement.SpaceEvenly,
            modifier = Modifier
                .fillMaxWidth()
                .padding(10.dp)
        ) {
            Button(onClick = {
                viewModel.insertStudent(
                    Student(
                        studentName,
                        studentScore.toInt()
                    )
                )
                searching = false
            }) {
                Text("C-新增")
            }

            Button(onClick = {
                searching = true
                viewModel.findStudent(studentName)
            }) {
                Text("R-搜尋")
            }

            Button(onClick = {
                searching = false
                viewModel.updateStudent(
                    studentName,
                    studentScore.toInt()

                )
            }) {
                Text("U-修改")
            }

            Button(onClick = {
                searching = false
                viewModel.deleteStudent(studentName)
            }) {
                Text("D-刪除")
            }
        }

        LazyColumn(
            Modifier
                .fillMaxWidth()
                .padding(10.dp)
        ) {
            val list = if (searching) searchResults else allStudents

            item {
                TitleRow(
                    head1 = "ID",
                    head2 = "學生姓名",
                    head3 = "成績"
                )
            }

            items(list) { student ->
                StudentRow(
                    id = student.id,
                    name = student.studentName,
                    score = student.score,
                    onItemClick = { studentName = student.studentName }
                )
            }
        }

    }
}

@Composable
fun TitleRow(head1: String, head2: String, head3: String) {
    Row(
        modifier = Modifier
            .background(MaterialTheme.colors.primary)
            .fillMaxWidth()
            .padding(5.dp)
    ) {
        Text(
            head1, color = Color.White,
            modifier = Modifier
                .weight(0.1f)
        )
        Text(
            head2, color = Color.White,
            modifier = Modifier
                .weight(0.2f)
        )
        Text(
            head3, color = Color.White,
            modifier = Modifier.weight(0.2f)
        )
    }
}

@Composable
fun StudentRow(id: Int, name: String, score: Int, onItemClick: () -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(5.dp)
            .clickable { onItemClick() }
    ) {
        Text(
            id.toString(), modifier = Modifier
                .weight(0.1f)
        )
        Text(name, modifier = Modifier.weight(0.2f))
        Text(score.toString(), modifier = Modifier.weight(0.2f))
    }
}

@Composable
fun CustomTextField(
    title: String,
    textState: String,
    onTextChange: (String) -> Unit,
    keyboardType: KeyboardType
) {
    OutlinedTextField(
        value = textState,
        onValueChange = { onTextChange(it) },
        keyboardOptions = KeyboardOptions(
            keyboardType = keyboardType
        ),
        singleLine = true,
        label = { Text(title) },
        modifier = Modifier.padding(10.dp),
        textStyle = TextStyle(
            fontWeight = FontWeight.Bold,
            fontSize = 20.sp
        )
    )
}

這個網誌中的熱門文章

2023 最新入門零基礎 Kotlin教學【從零開始學 Kotlin 程式設計】Kotlin 教學課程目錄 (Android Kotlin, IntelliJ IDEA, Android Studio, Android APP 開發教學)

2022 最新入門零基礎 Flutter教學 【Flutter 程式設計入門實戰 30 天】Flutter 教學課程目錄 (IntelliJ IDEA 開發教學)

nano 文字編輯器

Android Studio 歷代版本下載點

16天記下7000單字