Hilt Project 생성을 위한 gradle 구성
project => gradle
buildscript {
ext {
compose_ui_version = '1.3.3'
hilt_version = '2.44'
}
}
plugins {
....
// hilt-android-gradle-plugin
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
app => gradle
plugins {
....
// hilt-android-gradle-plugin
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
android {
...
}
dependencies {
.......
// Hilt-dagger
//noinspection GradleDependency
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
}
// Hilt
kapt {
correctErrorTypes true
}
kapt(Kotlin 주석 처리 도구)를 사용하면 Kotlin 코드에서 자바 주석 프로세서를 사용할 수 있습니다. 자바 주석 프로세서에 Kotlin 지원이 없는 경우에도 가능합니다. Kotlin 파일에서 프로세서가 읽을 수 있는 자바 스텁이 생성되어 자바 주석 프로세서의 사용이 지원됩니다. 이 스텁 생성은 비용이 많이 드는 작업으로 빌드 속도에 큰 영향을 줍니다.
KSP(Kotlin Symbol Processing)는 kapt의 Kotlin 우선 대안입니다. KSP는 Kotlin 코드를 직접 분석하기 때문에 시간이 최대 2배 빠릅니다. 또한 Kotlin의 언어 구성을 더 잘 이해합니다.
kapt는 현재 유지보수 모드로 전환되었으므로 가능한 경우 kapt에서 KSP로 이전하는 것을 권장합니다. 대부분의 경우 이전을 진행하려면 프로젝트의 빌드 구성만 변경하면 됩니다.
이전이 진행되는 동안 프로젝트에서 kapt와 KSP를 함께 실행할 수 있으며, 이전은 모듈 및 라이브러리별로 실행할 수 있습니다.
Hilt는 kapt를 사용 ??? ksp만 설정하면 kapt를 설정하라고 함...
=========================
Room Gradle 구성
module gradle
plugins {
...
// hilt-android-gradle-plugin
id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false // annotation관련
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
app => gradle
plugins {
...
// hilt-android-gradle-plugin
id 'kotlin-kapt' // 아래 내용 참조(kapt보다 ksp를 권장) => hilt를 위해 필요
id 'com.google.devtools.ksp'
id 'com.google.dagger.hilt.android'
}
android {
...
// room.schemaLocation: Configures and enables exporting database schemas
// into JSON files in the given directory. See Room Migrations for more information.
// room.incremental: Enables Gradle incremental annotation processor.
javaCompileOptions {
annotationProcessorOptions {
arguments += [
"room.schemaLocation": "$projectDir/schemas".toString(),
"room.incremental" : "true"
]
}
...
}
dependencies {
...
// Room
def room_version = "2.5.1"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// To use Kotlin annotation processing tool (kapt)
kapt "androidx.room:room-compiler:$room_version"
// To use Kotlin Symbol Processing (KSP)
ksp "androidx.room:room-compiler:$room_version"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4"
// Hilt
implementation "com.google.dagger:hilt-android:2.44.2"
kapt "com.google.dagger:hilt-compiler:2.44.2"
}
// Hilt
kapt {
correctErrorTypes true
}
※ Coroutine도 같이 설정 필요
* 공식 문서에는 없으나 전체 완료 후 실행하면 아래 내용 추가 하라고 애러 나옴.
implementation "androidx.room:room-ktx: @room_version"
===========================================================
Hilt Project(DI) 기본 구성
1. Application 생성(MainActivity와 같은 위치에 생성)
// dependency => 모든 앱에 접근 가능 component scan을 위한 annotation => @Autowired
@HiltAndroidApp
class NoteApplication : Application() {}
2. manifests 수정(추가)
......
<application
android:name=".NoteApplication"
......
</application>
3. MainActivity 수정(Annotation 추가)
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
4. AppModule 생성(di 폴더)
@InstallIn(SingletonComponent::class)
@Module
object AppModule {}
[ 노트 App을 통한 Room DI 구현 ]
1. DAO 구성
SQLite DB는 기본적으로 내부에 구성되어 별도로 DB를 생성할 필요는 없음
외부 DB 연계는 추후에 ...
DB를 생성하고 기존 Data를 DB에 맞게 변경(Annotation) 구성
data/Note.kt(Data model) 변환
@Entity(tableName = "notes_tbl" )
data class Note(
@PrimaryKey
val id: UUID = UUID.randomUUID(), // DB에 저장하기 위한 변환 필요
@ColumnInfo(name = "note_title" )
val title: String,
@ColumnInfo(name = "note_description")
val description: String,
@ColumnInfo(name = "note_entry_date")
val entryDate: Date = Date.from(Instant.now()) // DB에 저장하기 위한 변환 필요
)
/*data class Note(
val id: String = UUID.randomUUID().toString(),
val title: String,
val description: String,
val entryDate: LocalDateTime = LocalDateTime.now()
)*/
data/Database.kt 생성
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDatabaseDao
}
di/AppModule.kt 생성
@InstallIn(SingletonComponent::class)
@Module
object AppModule {
@Singleton
@Provides
fun provideNotesDao(noteDatabase: NoteDatabase) : NoteDatabaseDao
= noteDatabase.noteDao()
@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext context: Context) : NoteDatabase
= Room.databaseBuilder(
context,
NoteDatabase::class.java,
"notes_db"
)
.fallbackToDestructiveMigration()
.build()
}
data/DatabaseDao.kt 생성
@Dao
interface NoteDatabaseDao {
// Flow: An asynchronous data stream that sequentially
// emits values and completes normally or with an exception.
@Query("SELECT * from notes_tbl")
fun getNotes():
Flow<List<Note>>
// suspend를 하면 coroutine을 타게됨.
@Query("SELECT * FROM notes_tbl where id =:id")
suspend fun getNoteById(id: String): Note
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(note: Note)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(note: Note)
@Query("DELETE FROM notes_tbl")
suspend fun deleteAll()
@Delete
suspend fun deleteNote(note: Note)
}
di/AppModule.kt 내용 적용
@InstallIn(SingletonComponent::class)
@Module
object AppModule {
@Singleton
@Provides
fun provideNotesDao(noteDatabase: NoteDatabase) : NoteDatabaseDao
= noteDatabase.noteDao()
@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext context: Context) : NoteDatabase
= Room.databaseBuilder(
context,
NoteDatabase::class.java,
"notes_db"
)
.fallbackToDestructiveMigration()
.build()
}
2. Repository 생성(옵션이나 꼭 필요함)
repository/NoteRepository
dependency injecting을 해야함: @Inject constructor(private val noteDatabaseDao: NoteDatabaseDao){}
class NoteRepository @Inject constructor(private val noteDatabaseDao: NoteDatabaseDao){
suspend fun addNote(note: Note) = noteDatabaseDao.insert(note = note)
suspend fun updateNote(note: Note) = noteDatabaseDao.update(note)
suspend fun deleteNote(note: Note) = noteDatabaseDao.deleteNote(note)
suspend fun deleteAllNotes() = noteDatabaseDao.deleteAll()
fun getAllNotes() = noteDatabaseDao.getNotes().flowOn(Dispatchers.IO)
.conflate()
suspend fun getNoteById(note: Note) = noteDatabaseDao.getNoteById(note.id.toString())
}
screen/NoteViewModel.kt => 내용 수정(거의 다시 만들어야함)
dependency injecting을 해야함:@Inject constructor(private val repository: NoteRepository) : ViewModel()
@HiltViewModel
class NoteViewModel @Inject constructor(private val repository: NoteRepository) : ViewModel() {
// private var noteList = mutableStateListOf<Note>()
private val _noteList = MutableStateFlow<List<Note>>(emptyList())
val noteList = _noteList.asStateFlow()
init {
// noteList.addAll(NotesDataSource().loadNotes())
viewModelScope.launch(Dispatchers.IO) {// 동시 작업을 위함
repository.getAllNotes().distinctUntilChanged()
.collect { listOfNotes ->
if (listOfNotes.isEmpty()) {
Log.d("empty", ": Empty list")
} else {
_noteList.value = listOfNotes
}
}
}
}
fun addNote(note: Note) = viewModelScope.launch {
repository.addNote(note)
}
fun updateNote(note: Note) = viewModelScope.launch {
repository.updateNote(note)
}
fun removeNote(note: Note) = viewModelScope.launch {
repository.deleteNote(note)
}
fun getAllNotes() = viewModelScope.launch(Dispatchers.IO) {// 동시 작업을 위함
repository.getAllNotes().distinctUntilChanged()
.collect { listOfNotes ->
if (listOfNotes.isEmpty()) {
Log.d("empty", ": Empty list")
} else {
_noteList.value = listOfNotes
}
}
}
suspend fun getNoteById(note: Note) = viewModelScope.launch {
repository.getNoteById(note)
}
}
3. UI 수정
screen/NoteScreen.kt => 거의 변경할게 없음(일부 데이터 포맷 변환만 필요 => 안하면 애러 발생)
@OptIn(ExperimentalComposeUiApi::class)
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
@Composable
fun NoteScreen(
notes: List<Note>,
onAddNote: (Note) -> Unit,
onRemoveNote: (Note) -> Unit,) {
var title by remember { mutableStateOf("") }
var description by remember { mutableStateOf("") }
val keyboardController = LocalSoftwareKeyboardController.current
Scaffold(topBar = {
TopAppBar(modifier = Modifier.padding(4.dp), backgroundColor = Color.LightGray) {
Text("Note App for viewModel exercise")
}
}) {
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top
) {
NoteInputTextField(
text = title,
label = "title",
onTextChange = { title = it })
Spacer(modifier = Modifier.height(8.dp))
NoteInputTextField(
text = description,
label = "note",
onTextChange = { description = it})
NoteButton(text = "Click to Save",
onClick = {
if (title.isNotEmpty() && description.isNotEmpty()) {
// list에 저장, 추가 하기...
onAddNote(Note(title = title, description = description))
title = ""
description = ""
}
keyboardController?.hide() })
Divider()
LazyColumn() {
items(notes) { note ->
NoteRow(note = note, onNoteClicked = {
onRemoveNote(note)
})
}
}
}
}
}
@Composable
fun NoteRow(
modifier: Modifier = Modifier,
note: Note,
onNoteClicked: (Note) -> Unit,
) {
Surface(
modifier
.padding(4.dp)
.clip(RoundedCornerShape(topEnd = 20.dp, bottomStart = 20.dp))
.fillMaxWidth(),
color = Color.LightGray,
elevation = 8.dp ) {
Column(
modifier
.clickable { onNoteClicked(note) }
.padding(horizontal = 12.dp, vertical = 6.dp),
horizontalAlignment = Alignment.Start ) {
Text( note.title, style = MaterialTheme.typography.subtitle2 )
Text(text = note.description, style = MaterialTheme.typography.subtitle1)
Text( text = note.entryDate //ofPattern("EEE, d MMM")
.format(DateTimeFormatter.ofPattern("u-M-d(E) H:m")),
style = MaterialTheme.typography.caption )
}
}
}
@Composable
fun NoteButton(
text: String,
onClick: () -> Unit,
) {
Button(
onClick = onClick,
shape = RoundedCornerShape(40.dp),
modifier = Modifier
.padding(6.dp)
) {
Text(text = text, modifier = Modifier.padding(horizontal = 6.dp))
}
}
// Text( text = note.entryDate.format(DateTimeFormatter.ofPattern("u-M-d(E) H:m")),
Text(text = formatDate(note.entryDate.time), => db의 데이터를 스트링 포맷으로 변환
id, entryDate는 DB 데이터 포팻을 맞추기 위해 변환 필요(util/DateConverter, UUIDConverter)
class UUIDConverter {
@TypeConverter
fun fromUUID(uuid: UUID): String? {
return uuid.toString()
}
@TypeConverter
fun uuidFromString(str: String): UUID? {
return UUID.fromString(str)
}
}
class DateConverter {
@TypeConverter
fun timeStampFromDate(data: Date) : Long{ // java.Date to DB timestamp
return data.time
}
@TypeConverter
fun dateFromTimestamp(timestamp: Long) : Date?{ // DB timestamp to java.Date
return Date(timestamp)
}
}
database에 아래 내용을 추가..
@TypeConverters(DateConverter::class, UUIDConverter::class) // :: method reference 매소드 참조
@Database(entities = [Note::class], version = 1, exportSchema = false)
@TypeConverters(DateConverter::class, UUIDConverter::class) // :: method reference 매소드 참조
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDatabaseDao
}
NoteScreen.kt에서도 이에 맞게 변환을 위한 formatDate 생성
fun formatDate(time: Long) :String {
val date = Date(time)
val myFormat = SimpleDateFormat("y-M-d(E) H:M aaa", Locale.KOREA)
return myFormat.format(date)
}
//"EEE, d MMM hh:mm aaa" => Sun, 31 Oct 04:28 PM
//"y-M-d(E) H:M aaa", Locale.KOREA => 2023-4-2(일) 15:4 오후
NoteScreen.kt 내용 수정
Text(text = formatDate(note.entryDate.time),
style = MaterialTheme.typography.caption )
...
) {
Text(note.title)
Text(" : ${note.description}")
// Text(note.entryDate.format(DateTimeFormatter.ofPattern("u-M-d H:m", Locale.KOREA)))
Text(text = formatDate(note.entryDate.time))
}
}...
4. MainActivity 수정
입력 데이터를 기존 ViewModel에서 DB로 변경
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
RoomDiTheme {
Surface(color = MaterialTheme.colors.background) {
// val noteViewModel = viewModel<NoteViewModel>() 아래와 같은 것임
val noteViewModel: NoteViewModel by viewModels()
MyApp(noteViewModel = noteViewModel)
}
}
}
}
}
@Composable
fun MyApp(noteViewModel: NoteViewModel) {
// val noteList = noteViewModel.getAllNote()
val noteList = noteViewModel.noteList.collectAsState().value
NoteScreen(notes = noteList,
onAddNote = { noteViewModel.addNote(it) },
onRemoveNote = { noteViewModel.removeNote(it) }
)
}
애러 ...
Failed to resolve: androidx.room:room-ktx: 2.5.1
app gradle에서 빼서 sync하고 다시 넣어서 sync하면 애러 사라짐....
'Android-jetpack Compose' 카테고리의 다른 글
jetpack compose : (Step 2) ☞ ViewModle(NoteApp) (0) | 2023.04.02 |
---|---|
jetpack compose : (Step 1) Without ViewModle(NoteApp) (0) | 2023.04.02 |
jetpack compose - dependency injection, Room 참고 자료-사이트 (0) | 2023.04.02 |
Android Coroutine (1) | 2023.04.02 |
Android - Dependency Injection(Hilt)/Room 기본 개념 설정 (0) | 2023.04.01 |