๊ฐœ๋ฐœ ์ผ์ง€ ๐Ÿ’ป/Nutri Capture

Nutri Capture ๋ฐฑ์—”๋“œ - Hilt ๋„์ž…

interfacer_han 2025. 2. 1. 18:30

#1 ๊ฐœ์š”

#1-1 Hilt ๋„์ž…

๊ณ„ํšํ‘œ ์ƒ, Hilt ๋„์ž…์€ ๋์ž๋ฝ ๋‹จ๊ณ„์˜€๋‹ค. ํ•˜์ง€๋งŒ, ์ฝ”๋“œ๋ฅผ ์งœ๊ฐ€๋ฉฐ ์•ฑ์„ ๊ตฌํ˜„ํ•ด๋‚˜๊ฐ€๋Š” ๋ฐ์— ์—ฌ๋Ÿฌ ์ƒ์šฉ๊ตฌ ์ฝ”๋“œ๋“ค์ด ๋‚˜๋ฅผ ๊ฑฐ์Šฌ๋ฆฌ๊ฒŒ ํ–ˆ๋‹ค. Hilt๋กœ ํ˜„์กดํ•˜๋Š” ์ƒ์šฉ๊ตฌ ์ฝ”๋“œ ๊ทธ๋ฆฌ๊ณ  ์ž ์žฌ์ ์œผ๋กœ ๋ฐœ์ƒํ•  ์ƒ์šฉ๊ตฌ ์ฝ”๋“œ๋“ค์„ ์„ ์ œ์ ์œผ๋กœ ์ œ๊ฑฐํ•˜๋Š” ํŽธ์ด ๋” ์ข‹์„ ๊ฒƒ์ด๋ž€ ํŒ๋‹จ์„ ๋‚ด๋ ธ๋‹ค.

 

#1-2 ๊ธฐ๋ฐ˜ (๋ ˆํผ๋Ÿฐ์Šค)

 

[Android] Dagger2 - Hilt๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

#1 Hilt ๊ฐœ์š” Hilt๋ฅผ ์‚ฌ์šฉํ•œ ์ข…์† ํ•ญ๋ชฉ ์‚ฝ์ž…  |  Android Developers์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Hilt๋ฅผ ์‚ฌ์šฉํ•œ ์ข…์† ํ•ญ๋ชฉ ์‚ฝ์ž… ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์— ๊ธฐ๋ฐ˜ํ•ด, ๋ณธ ์•ˆ๋“œ๋กœ์ด๋“œ์— Hilt๋ฅผ ๋„์ž…ํ•œ๋‹ค.

 

#2 ์ฝ”๋“œ - ํ™˜๊ฒฝ ์„ค์ •

#2-1 ๋ชจ๋“ˆ ์ˆ˜์ค€ build.gradle.kts

plugins {
    ...
    // KSP ๋ฐ kapt (์–ด๋…ธํ…Œ์ด์…˜ ์ฝ๊ธฐ์šฉ)
    id("com.google.devtools.ksp")
    id("org.jetbrains.kotlin.kapt")
    // Hilt
    id("com.google.dagger.hilt.android")
}

android {
    ...
}

dependencies {

    ...

    // Hilt
    implementation(libs.hilt.android)
    kapt(libs.hilt.android.compiler)
    ksp(libs.hilt.compiler)
    implementation(libs.androidx.hilt.navigation.compose) // Hilt์™€ Jetpack Compose์˜ ViewModel์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (hiltViewModel() ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
}

#1-2์—์„œ ๋งํฌํ•œ ๋ ˆํผ๋Ÿฐ์Šค ๊ฒŒ์‹œ๊ธ€์˜ build.gradle๋งŒ์œผ๋กœ๋Š” ํ›„์ˆ ํ•  hiltViewModel()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค. hiltViewModel()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ "androidx.hilt:hilt-navigation-compose"๋ฅผ dependencies์— ๋„ฃ์—ˆ๋‹ค.

 

#2-2 Application ํด๋ž˜์Šค

package com.example.nutri_capture_new.di

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class HiltApplication : Application()

#1-2์— ์˜ํ•˜๋ฉด, Hilt๋Š” @HiltAndroidApp ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ Application ํด๋ž˜์Šค๋ฅผ ์š”๊ตฌํ•œ๋‹ค. ๋˜, ์ด Application ํด๋ž˜์Šค ๋ฐ ์˜์กด์„ฑ ์ฃผ์ž…์— ๊ด€๋ จ๋œ ๋ชจ๋“  ํด๋ž˜์Šค๋ฅผ ๋„ฃ์„ ํŒจํ‚ค์ง€์ธ "di" ํŒจํ‚ค์ง€๋ฅผ ์‹ ์„คํ–ˆ๋‹ค.

 

#2-3 AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name=".di.HiltApplication"
        ...>
        <activity
            ...
        </activity>
    </application>

</manifest>

๋ฐฉ๊ธˆ ๋งŒ๋“  Application ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

#3 ์ฝ”๋“œ - Activity

#3-1 Activity

...

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    // nutrientViewModel์ด Hilt์˜ viewModels()์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋„๋ก ์œ„์ž„(by). ๋”ฐ๋ผ์„œ ๋ฐ˜๋“œ์‹œ by ํ‚ค์›Œ๋“œ๋กœ ์„ ์–ธํ•ด์•ผํ•จ.
    private val nutrientViewModel: NutrientViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        setContent {
            NutricapturenewTheme {
                Scaffold(
                    ...
                    bottomBar = {
                        NutrientChatBar()
                    }
                ) { ... ->
                    Box(
                        ...
                    ) {
                        NutrientScreen()
                    }
                }
            }
        }
    }
}

@AndroidEntryPoint๊ฐ€ ์„ ์–ธ๋œ Activity์—์„œ ViewModel์„ ๋งŒ๋“ค๋ฉด, ์ด Activity ๋‚ด์˜ ๋ชจ๋“  ํ•˜์œ„ Composable์—์„œ ๋™์ผํ•œ ViewModel (์‹ฑ๊ธ€ํ†ค) ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณต์œ ํ•œ๋‹ค. ํ•˜์œ„ ์ปดํฌ์ €๋ธ” ํ•จ์ˆ˜์ธ NutrientChatBar()๋‚˜ NutrientScreen()์—์„œ nutrientViewModel๋ฅผ ๊ณต์œ ํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์–˜๊ธฐ๋‹ค.

 

#3-2 Activity์˜ ํ•˜์œ„ ์ปดํฌ์ €๋ธ”

@Composable
fun NutrientScreen(
    viewModel: NutrientViewModel = hiltViewModel()
) {
    ...
}

@Composable
fun NutrientChatBar(
    viewModel: NutrientViewModel = hiltViewModel()
) {
    ...
}

hiltViewModel()์„ ์‚ฌ์šฉํ•˜๋ฉด ํ˜„์žฌ Activity๋‚˜ Fragment์—์„œ Hilt๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ViewModel ์ธ์Šคํ„ด์Šค๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ€์ ธ์˜จ๋‹ค (Hilt๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ViewModel์ด๋ž€ @HiltViewModel ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ViewModel(#4-3)์„ ์˜๋ฏธํ•œ๋‹ค). hiltViewModel()์€ ViewModelStoreOwner(์˜ˆ: Activity, Fragment)์˜ ๋ฒ”์œ„ ๋‚ด์—์„œ ViewModel์„ ์ž๋™์œผ๋กœ ์ฐพ์•„ ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค. ์ด ์ฝ”๋“œ์—์„œ๋Š” #3-1์˜ nutrientViewModel๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค.

 

๋งŒ์•ฝ viewModelStoreOwner์— ํ•ด๋‹น ViewModel์ด ์—†๋‹ค๋ฉด, ๋‹ค์‹œ ๋งํ•ด ์—ฌ๊ธฐ์„  MainActivity ๋‚ด์— nutrientViewModel์ด ์—†๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ? ์ด ๊ฒฝ์šฐ hiltViewModel()์€ ์ƒˆ๋กœ์šด ViewModel ์ธ์Šคํ„ด์Šค๋ฅผ (์–ด๋””์—์„œ ๊ฐ€์ ธ์˜ค๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ ๊ทธ ์ž๋ฆฌ์—์„œ) ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ์—, ViewModel์˜ ์ƒ๋ช…์ฃผ๊ธฐ(๋ฒ”์œ„)๋Š” ํ•ด๋‹น ์ปดํฌ์ €๋ธ”์˜ ์ƒ๋ช…์ฃผ๊ธฐ์— ์ข…์†๋œ๋‹ค.

 

ํ”„๋กœ์ ํŠธ์— ์žˆ๋˜ ViewModelFactory๋Š” ์ด์ œ ์‚ญ์ œํ•œ๋‹ค. Hilt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์•Œ์•„์„œ(์•”์‹œ์ ์œผ๋กœ) ViewModel์— ์ธ์ˆ˜๋ฅผ ์ฃผ์ž…ํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

#4 ์ฝ”๋“œ - Room

#4-1 Database, DAO

package com.example.nutri_capture_new.db

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

@Database(
    entities = [
        Day::class,
        Meal::class
    ],
    views = [DayMealView::class],
    version = 1
)
@TypeConverters(Converters::class)
abstract class MainDatabase : RoomDatabase() {

    abstract val mainDAO: MainDAO

/* ์‚ญ์ œ
    companion object {
        @Volatile
        private var INSTANCE: MainDatabase? = null
        fun getInstance(context: Context): MainDatabase {
            synchronized(this) {
                var instance = INSTANCE
                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        MainDatabase::class.java,
                        "main_database"
                    ).build()
                    INSTANCE = instance
                }
                return instance
            }
        }
    }
/*
}

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ธฐ์กด ๋ฐฉ์‹์€ MainDabase์˜ companion object๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฑฐ์˜€๋‹ค. ์ด์ œ๋Š” Hilt๋ฅผ ํ†ตํ•ด ์•”์‹œ์ ์œผ๋กœ ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ๊ฒƒ์ด๋ฏ€๋กœ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.

 

package com.example.nutri_capture_new.di

import android.content.Context
import androidx.room.Room
import com.example.nutri_capture_new.db.MainDAO
import com.example.nutri_capture_new.db.MainDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): MainDatabase {
        return Room.databaseBuilder(
            context,
            MainDatabase::class.java,
            "main_database"
        ).build()
    }

    @Provides
    fun provideMainDAO(database: MainDatabase): MainDAO {
        return database.mainDAO
    }
}

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ DAO๋Š” @Inject ๋Œ€์‹  @Provides ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ๊ณต์‹ ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด, "ํด๋ž˜์Šค๊ฐ€ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณต๋˜๋ฏ€๋กœ ํด๋ž˜์Šค๋ฅผ ์†Œ์œ ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ(Retrofit, OkHttpClient ๋˜๋Š” Room ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๊ฐ™์€ ํด๋ž˜์Šค) ๋˜๋Š” ๋นŒ๋” ํŒจํ„ด์œผ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ"์—๋Š” ์ƒ์„ฑ์ž ์‚ฝ์ž…(@Inject ์–ด๋…ธํ…Œ์ด์…˜)์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค (์ฐธ์กฐ: @Provides์˜ ๋ชฉ์ ).

 

์ด ์ฝ”๋“œ ์ดํ›„๋กœ, ์ด์ œ Provides ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ DAO๊ฐ€ ์ฃผ์ž…๋œ๋‹ค. provideDatabase()๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ์Šคํ„ด์Šค๋ฅผ provideMainDAO()์— ์ „๋‹ฌํ•˜๊ณ , provideMainDAO()๋Š” DAO ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฑ‰๋Š”๋‹ค.

 

#4-2 Repository

package com.example.nutri_capture_new.db

import kotlinx.coroutines.flow.Flow
import java.time.LocalDate
import javax.inject.Inject

class MainRepository @Inject constructor(private val dao: MainDAO) {
    ...
}

provideMainDAO()์— ์˜ํ•ด ์ธ์ˆ˜ dao๊ฐ€ ์ฃผ์ž…๋œ๋‹ค.

 

#4-3 ViewModel

...

@HiltViewModel
class NutrientViewModel @Inject constructor(private val repository: MainRepository) : ViewModel() {
    ...
}

์ธ์ˆ˜ repository์˜ ํด๋ž˜์Šค๋ช… MainRepository๋ฅผ Hilt๊ฐ€ ๊ฐ์ง€ + ๊ฒ€์ƒ‰ํ•˜์—ฌ ๊ทธ ์ธ์Šคํ„ด์Šค๋ฅผ ์•Œ์•„์„œ ์ฃผ์ž…ํ•ด์ค€๋‹ค.

 

#5 ์™„์„ฑ๋œ ์•ฑ

#5-1 ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„

๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„œ ๋‹ค๋ฃฌ ํด๋ž˜์Šค๋“ค์˜ ์˜์กด์„ฑ์„ ๋„์‹ํ‘œ๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์œ„์™€ ๊ฐ™๋‹ค. ํ™”์‚ดํ‘œ๋Š” ํด๋ž˜์Šค ๊ฐ„์˜ ์ข…์†์„ฑ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, MainActivity๋Š” NutrientScreen์— ์ข…์†๋œ๋‹ค. ์ข…์†์˜ ์‚ฌ์ „์  ์˜๋ฏธ๋Š” '์ž์ฃผ์„ฑ์ด ์—†์ด ์ฃผ๊ฐ€ ๋˜๋Š” ๊ฒƒ์— ๋”ธ๋ ค ๋ถ™์Œ'์ด๋‹ค. ์ข…์†์€ '์•Œ์•„์•ผ ํ•œ๋‹ค'๋ผ๋Š” ๋ง๋กœ๋„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ MainActivity๋Š” NutrientScreen์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•œ๋‹ค. ๋ฐ˜๋ฉด, NutrientScreen์€ MainActivity๋ฅผ ๋ชฐ๋ผ๋„ ๋œ๋‹ค. NutrientScreen์„ ์„ค๊ณ„ํ•  ๋• MainActivity์—์„œ ๋ญ˜ ์–ด๋–ป๊ฒŒ ํ• ์ง€ ์ „ํ˜€ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค (๋Œ€์‹ , NutrientScreen์€ NutrientViewModel์— ๋Œ€ํ•ด ์ข…์†์ ์ด๋ฏ€๋กœ NutrientViewModel์„ ์ฐธ์กฐํ•˜๋ฉฐ ์„ค๊ณ„ํ•ด์•ผ ํ•œ๋‹ค). '์•Œ์•„์•ผ ํ•˜๋Š” ์ชฝ'์—์„œ '๋ชฐ๋ผ๋„ ๋˜๋Š” ์ชฝ'์œผ๋กœ ํ™”์‚ดํ‘œ๋ฅผ ์ด์€ ๊ฒƒ์ด ์œ„ ๋„์‹๋„๋‹ค.

 

#5-2 ์ด ๊ฒŒ์‹œ๊ธ€ ์‹œ์ ์˜ Commit

 

GitHub - Kanmanemone/nutri-capture-new

Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.

github.com

 

#5-3 ๋ณธ ํ”„๋กœ์ ํŠธ์˜ ๊ฐ€์žฅ ์ตœ์‹  Commit

 

GitHub - Kanmanemone/nutri-capture-new

Contribute to Kanmanemone/nutri-capture-new development by creating an account on GitHub.

github.com