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