왜 이 글을 적게 되었는가..

 

팝업에서 ChipGroup을 사용할 일이 생겨서 열심히 코딩하고 실행까지 했는데

팝업을 열려고 할 때 갑자기 아래와 같은 오류가 생겼다.

 

The style on this component requires your app theme to be Theme.AppCompat (or a descendant).

 

오 앱 테마가 Theme.AppCompat을 상속받아야 하는가보다..

 

하지만 확인 결과 나는 application theme도 Material 테마였고, App Theme을 AppCompat을 상속받게 하였지만 문제는 나아지지 않았다.

 

그리고 30분째..나는 계속되는 구글링으로 지쳐가고있었고..

 

그러던 중 '혹시'나 해서

 

val dialog = ListDialog(baseContext)

val dialog = ListDialog(this@MainActivity)

로 바꿔봤더니..

 

잘 된다..?

 

도대체 이런 일은 왜 생긴 걸까...? 에서 시작한 오늘의 포스팅..

 

baseContext, applicationContext, 그리고 this@MainActivity는 무엇이 다른가!

 

지금 시작합니다!

 


1. this@MainActivity

가장 먼저, 가장 쉬운 this@MainActivity 먼저 살펴보겠다.

해석해보면 '이거@MainActivity'다.

그렇다!

이건 그냥 this 라는 예약어에다가.. 특별히 'MainActivity 말이야!' 라는 걸 붙여준 것이다.

메인액티비티 내에서 this만 써도 무관한 경우가 있지만, 경우에 따라 this가 꼭 MainActivity를 가리키지 않을 때가 있기 때문이다.

 

그렇다면 이제 this가 무엇인지를 확인해보자.

this는 해당 클래스 내에서 자기 자신의 객체를 반환할 때 쓰는 예약어라고 보면 된다.

 

따라서!

View.OnClickListener() {
	fun onClick(v: View) {
		Toast.makeText(this, "이건 오류남", Toast.LENGTH_SHORT).show()
    }
}

이 경우에는 this가 OnClickListener의 객체를 반환하기 때문에 오류가 난다.

this@MainActivity 처럼 사용하여야 한다.

 

결론 : this@MainActivity는 해당 액티비티의 객체를 반환하는 것이다. Activity는 Context를 상속받기 때문에 context를 대체해서 쓸 수 있는 거고..


2. baseContext

일단 이 녀석도 현재 액티비티 또는 서비스의 context를 반환한다.

'일반적으로' Toast나 Dialog같이 context가 필요한 개체를 만들 때 사용된다.

액티비티나 서비스와 연결되어있기 때문에 해당 액티비티나 서비스가 종료되면 함께 사라진다.

 

사실 이 녀석과 this@MainActivity의 차이를 잘 몰랐다.

그래서 위와 같은 사단이 났던 것..

 

this@MainActivity와 baseContext의 차이는...

현재 액티비티와 관련이 있다! 무적권 현재 액티비티를 넘겨줘야 한다! => this

일반적인 목적이다! 특정 액티비티와 연관이 있는 건 아니다! => baseContext

 

그리고 불러오는 곳도 다르다.

안드로이드 스튜디오에서 baseContext를 어디서 불러오냐 찾아보면

ContextWrapper 클래스에서 불러온다는 걸 알 수 있다.

 

this@MainActivity는 해당 Activity를 반환한다.

Activity는 ContextWrapper를 상속받은 ContextThemeWrapper를 상속받는다.

 

여기서 더 나아가서..

ContextWrapper.getBaseContext()는 open 키워드가 없는 것으로 볼 때 이는 오버라이드가 불가능하다는 것을 알 수 있다.

즉 getBaseContext()는 ContextWrapper단계에서 불러오지, 하위 Activity 등에서 불러오는 게 아니라는 것..

 

그렇기에 테마에 대한 값을 가지고 있지 않기에

AppCompat 테마가 필수인 ChipGroup은

'??님 이거 사용하려면 AppCompat 테마가 필요하다요!!'

하면서 에러를 내뿜었던 것이다.


3. applicationContext

이 녀석은 어플리케이션 단위의 context다. 

특정 액티비티에 국한되지 않은 Singleton Context라고 한다.

수명이 긴 개체에 넣어줘야할 때는 applicationContext를 사용하면 된다. 

예를 들어 글로벌 구성 설정, 애플리케이션 기본 설정 및 시스템 서비스와 같은 애플리케이션 수준 리소스 및 서비스에 대한 액세스를 제공하는 데 사용된다고 한다.

 

baseContext와 this@MainActivity가 특정 개체의 생명주기동안에만 유효하다고 한다면

applicationContext는 애플리케이션이 시작될 때 생성되고 애플리케이션의 전체 수명 주기 동안 활성 상태를 유지한다.

 

그렇기때문에 메모리 누수가 발생할 수 있으며 개발 시 이를 충분히 염두에 두어야 한다.

 


다 적고 났더니 뭔가 큰 걸 놓쳤다는 생각이 든다.

 

그렇다.

 

context가 뭔지에 대해서는 정리하지 않은 것이다.

 

context란? 직역하면 '문맥' 이런 뜻인데..

문맥이라는 건 '서로 이어져 있는 문장의 앞뒤 관계'를 말한다.

 

일반적으로 리소스에 액세스할 때, Activity를 시작할 때, 시스템 서비스에 액세스할 때, 팝업, 토스트 등의 개체를 만들 때 사용하는 클래스라고 보면 된다.

 

뭔가랑 뭔가를 이어줄 때 필요한 녀석이구나 정도로 이해하고 넘어가면 될 것 같다.

 

 

 


참고 :

 

chatGPT

https://mrgamza.tistory.com/197

 

안드로이드 스튜디오 플라맹고 버전을 설치하고...

Gradle 8.0.0으로 변경하고 난 후 자바 버전과 일치하지 않는다는 오류 메세지를 받았음.

 

오류 메세지 : 

'compileDebugJavaWithJavac' task (current target is 1.8) and 'kaptGenerateStubsDebugKotlin' task (current target is 19) jvm target compatibility should be set to the same Java version.

 

Module 단계의 build.gradle로 들어가서 compileOptions와 kotlinOptions를 아래와 같이 바꿔주었다.

(기존에는 VERSION_1_8, 1.8로 되어있었다)

compileOptions {
    sourceCompatibility JavaVersion.VERSION_19
    targetCompatibility JavaVersion.VERSION_19
}
kotlinOptions {
    jvmTarget = '19'
}

 

추가로 ProjectStructure에 들어가서 Gradle 버전과 Java 버전이 잘 맞게 들어가있나도 확인해주었다.

File-ProjectStructure (단축키 Ctrl+Alt+Shift+S)

 

그래들 버전은 이렇게 확인하면 되고..

 

 

자바 버전은 여기서 확인하면 된다.

 

컴퓨터에 자바 20이 깔려있어도 Gradle JDK에는 17짜리 다운받아서 쓸 수 있음..

 

다운받는 법.. GradleJDK 드롭박스 클릭 후 Download JDK를 클릭한다.

 

 

그런 다음 Version에서

아까 오류 메세지에서 나왔던

'kaptGenerateStubsDebugKotlin' task (current target is 19)

이 숫자(19)를 선택한 다음 아래 Vender에서 아무거나 선택하고 Download를 누른다.

 

 

그런다음 다 OK를 누르고 창을 닫으면 끝..

 

 

 

 

 

 

<참고> 그래들 버전 별 자바 버전(출처 : https://docs.gradle.org/current/userguide/compatibility.html)

 

paging3 라이브러리를 적용하면서 발생한 실수 ㅜ 

 

[문제가 된 코드]

lifecycleScope.launch {        
    viewModel.topicFlow.collectLatest {
        topicAdapter.submitData(it)
    }
            
    viewModel.photoFlow(TopicData.TOPIC_IDX_ALL).collectLatest {
        val isFirst = photoAdapter.itemCount == 0
        photoAdapter.submitData(it)
        if(isFirst){
            checkEmpty()
        }
    }
}

[현상]

viewModel.photoFlow().. 부분이 동작하지 않았음.

 

[이유]

스택오버플로우 

You will need different coroutines, since collect() is a suspending function that suspends until your Flow terminates.

 

아래는 단톡방에서 말씀해주신 이유들..

야옹이님 : 계속 수집하기때문에 Blocking되서 아래것이 실행안되는게 아닐까요

이상해씨님 : collect가 suspend function이라서 코루틴 빌더 안에 있어야해요. collect가 suspend function인데 flow가 끝이 없다면 suspend가 끝나지 않겠죠

Rokace님 : Stateflow는 lifecyclescope에 기반하여 데이터의변화를 관측하는 무한루프 쓰레드 하나를 만듭니다.
Stateflow 하나당 쓰레드 하나죠
그래서 collect는 destroy이전까지 동작합니다.

 

 

[개선한 코드]

lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.topicFlow.collectLatest {
                        topicAdapter.submitData(it)
                    }
                }

                launch {
                    viewModel.photoFlow(TopicData.TOPIC_IDX_ALL).collectLatest {
                        val isFirst = photoAdapter.itemCount == 0
                        photoAdapter.submitData(it)
                        if(isFirst){
                            checkEmpty()
                        }
                    }
                }
            }
        }

별도의 코루틴으로 launch 해 주었다.

PatternTest_hilt.zip
0.11MB

 

이전에 aac_mvvm 패턴 프로젝트에서 

메인화면에서 Retrofit 객체를 만들어서 ViewModel의 생성자로 넘겨주는 식으로 구현했는데

뭔가 찜찜했다.

 

뷰에서는 비즈니스 로직을 알 수 없게 구현해야하는 거 아닌가? 싶어서...

 

그래서 해바라기 예제 소스랑 비교해보니

해바라기 예제 소스는 Hilt가 적용되어있는 것을 알게 되었다.

 

그래서 나도 Hilt를 적용해보았다!

 

java랑 코틀린이랑 달라서 ViewModel 생성 시에는 어떻게 해야할 지 몰라서 기존과 똑같이 해 줬음..

방법이 있다면 알려주세용~

 

https://velog.io/@haanbink/Android-Hilt-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

EditText에 텍스트 입력 후 버튼 눌러서 리스트에 추가

리스트 항목을 롱클릭해서 리스트에서 제외

하는 예제이다.

AACMVVMTest.zip
0.11MB

 

RecyclerView에

AAC ViewModel + LiveData + ListAdapter를 곁들인..

 

나의 작고 소듕한 예제..

 


데이터 바인딩도 추가했다.

 

AACMVVMTest_databinding.zip
0.11MB

 

참고 링크 : 

https://velog.io/@hygge/Android-RecyclerView%EC%97%90-DataBinding-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

 

[Android] RecyclerView에 DataBinding 적용하기

태그들을 RecyclerView로 만들고 DataBinding을 곁들여서 Drawable 이미지와 텍스트를 변경해줄 것이다. 파일 구조는 위와 같다.RecyclerView에 들어가는 정보인 이미지와 텍스트를 클래스로 정의해준다. 이

velog.io

https://improve777.medium.com/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B0%94%EC%9D%B8%EB%94%A9-two-way-binding-%EC%9B%90%EB%A6%AC-4317396728ff

 

데이터바인딩 two-way binding 원리

EditText 로 알아보는 BindingAdapter

improve777.medium.com

https://jwsoft91.tistory.com/67?category=815797 

 

[Java]  DataBinding  +  LiveData  +  BindingAdapter

DataBinding + LiveData + BindingAdapter build.gradle (Module: app) android { dataBinding { enabled = true } } BindingAdapters.java package com.jwsoft.javaproject; import android.view.View; import androidx.annotation.ColorInt; import androidx.databinding.Bi

jwsoft91.tistory.com

 

+ Recent posts