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
        )
    )
}

這個網誌中的熱門文章

16天記下7000單字

nano 文字編輯器

最新入門零基礎 Java 教學【從零開始學 Java 程式設計】Java教學課程目錄 (IntelliJ IDEA 開發教學)

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

【從零開始學 Java 程式設計】佈局管理器 - BoxLayout