Android-jetpack Compose

jetpack compose-Retrofit, JSON

slow333 2023. 4. 4. 11:11

Project 생성

project gradle: hilt 적용 

buildscript {
    ext {
        compose_ui_version = '1.3.3'
        hilt_version = '2.44'
    }
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.4.2' apply false
    id 'com.android.library' version '7.4.2' apply false
    id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
    id 'com.google.dagger.hilt.android' version '2.44' apply false
}

app gradle : Retrofit, Gson conveter, Hilt, Coroutine, Coroutine Lifecycle scopes 적용

plugins {
...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
...
    composeOptions {
        kotlinCompilerExtensionVersion '1.4.4'
    }
...
}

dependencies {
    // Hilt-dagger
    implementation "com.google.dagger:hilt-android:$hilt_version"

    kapt "com.google.dagger:hilt-compiler:$hilt_version"
//  implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03" => 필요 없음 있으면 애러남
    kapt "androidx.hilt:hilt-compiler:1.0.0"
    implementation "androidx.hilt:hilt-navigation-compose:1.0.0"

    // 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'

    // Coroutine Lifecycle scopes
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'

    // retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    // Gson converter
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

...
}

build error : ... dagger.hilt.android.internal.lifecycle.DefaultActivityViewModelFactory' could not be resolved.

https://salmonpack.tistory.com/34

원인은 바로 implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"

build.gradle(:app)

dependencies {
	...
   // hilt
    implementation 'com.google.dagger:hilt-android:2.44'
    kapt 'com.google.dagger:hilt-compiler:2.44'
    kapt 'com.google.dagger:hilt-android-compiler:2.44'
//    implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03"
  	...
}

Hilt 2.31에서 ViewModel 주입 방법이 바뀌었다고 합니다.

이제는 androidx.hilt:hilt-lifecycle-viewmodel 라이브러리 없이도

@HiltViewModel만 사용한다면 문제가 없다고 하네요.

=====================================

project package : component, data, di, model, network, repository, screen, util

 

model > json import

json을 인터넷에서 임포트하기 위해서는 
file > setting > Plugins > "Json to kotlin Class" => 설치 , 재시작

new > kotlin data class file from json
> 화면에 우클릭 > Retrieve content from Http url > URL을 입력

class name : Question => QuestionItem 이 자동 생성됨

프로젝트에서 사용하는 json : https://raw.githubusercontent.com/itmmckernan/triviaJSON/master/world.json

root> Application 파일 생성(@HiltAndroidApp 추가)

@HiltAndroidApp
class TriviaApplication : Application() {
}

manifests 수정(인터넷 접속 허용, application 추가)

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

    <application
        android:name=".TriviaApplication"
        ...
    </application>

</manifest>

di> AppModule 생성(object , singletone)

util> constants 생성(Base url)

object Constants {
   // https://raw.githubusercontent.com/itmmckernan/triviaJSON/master/world.json
   const val BASE_URL = "https://raw.githubusercontent.com/itmmckernan/triviaJSON/master/"
}

network> QuestionApi 생성(singletone, interface)

@Singleton
interface QuestionApi {

   @GET("world.json")
   suspend fun getAllQuestions(): Question
}

di> AppModule : Retrofit 객체 생성

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

   @Singleton
   @Provides
   fun provideQuestionApi(): QuestionApi {
      val retrofit = Retrofit.Builder()
         .baseUrl(Constants.BASE_URL)
         .addConverterFactory(GsonConverterFactory.create())
         .build()
//      val api = retrofit.create(QuestionApi::class.java)
//      val user: Response<User> = api.getUser().execute()
      return retrofit.create(QuestionApi::class.java)
   }
}

 

repository > QuestionRepository 생성(Inject)

class QuestionRepository @Inject constructor(private val api: QuestionApi) {
   private val listOfQuestions
//   = ArrayList<QuestionItem> (emptyList())
   = DataOrException<ArrayList<QuestionItem>,
           Boolean,
           java.lang.Exception>()
}

data> DataOrException 생성(wrapper class)

data class DataOrException <T, Boolean, E: Exception> (
   var data : T? = null,
   val loading: Boolean? = null,
   var e: E? = null   )

==> wrapper class를 만드는 이유는 받아온 데이터에 추가 적인 정보를 넣어서 쉽게 변경 가공하기 위함

===== 여기 까지 하면 기본 적으로 데이터를 가져오는 것은 되는 것임(json -> kotlin object) =====

MainActivity에 데이터 적용하기

viewModel을 가지고 와서 데이터를 적용(기본적으로 hiltViewModel을 적용하면 됨)

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContent {
         Json_Retrofit_triviaTheme {
            // A surface container using the 'background' color from the theme
            Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
               HomeScreen()
            }
         }
      }
   }
}

@Composable
fun HomeScreen(viewModel: QuestionViewModel = hiltViewModel() ) {
   Questions(viewModel)
}

@Composable
fun Questions(viewModel: QuestionViewModel) {
   val questions = viewModel.dataWrapped.value.data?.toMutableList()

   if (viewModel.dataWrapped.value.loading == true) {
      Log.d("로딩", "Questions  Loading...")
   } else {
      Log.d("로딩", "Questions  Loading.. STOPPED...")
      questions?.forEach { item ->
         Log.d("태그", "Questions : ${item.question}, ${item.category}")
      }
   }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
   Json_Retrofit_triviaTheme {
      HomeScreen()
   }
}

화면 구성