메모

timothy jeong·2021년 12월 20일
0

Android 개발 기록

목록 보기
2/8

웹뷰

앱 내부 웹뷰라는게 있음! 블로그

바텀시트 다이얼로그

바텀시트 다이얼로그라는게 있음! 블로그

여러 live data 를 하나의 조건으로

MediatorLiveData 를 이용하면 두개의 LiveData 의 상태를 이용해서 하나의 LiveData 를 만들 수 있음. 약관 동의할때 유용할듯 블로그글 로는 보면 잘 이해가 안되서 공식문서를 봤음, 그래도 잘 이해가 안되서 이 블로그 를 봤는데 엄청 정리가 잘되어 있음

access token 인증보내기

accessToken 을 헤더에 넣어서 보내려면 OkHttpClient 를 이용하는게 편함 retrofitr 과 OkHttp3, 재인증 관련글
로그까지

이미지와 텍스트가 같이 있는 버튼

이미지와 text 를 같이 사용하는 경우에는 materialButton 을 이용하는게 내부 컴포넌트의 포지션을 정하는데 좋음.

datatime issue

안드로이드에서 Datetime 을 쓸때 LocalDateTime 은 API 24 부터 적용되므로, 하위 호환성을 고려한다면 Calendar 를 이용해야한다.

인터셉터에서 json response 파싱하기

API 요청시 status 가 header 부분이 아닌 body 부분에 담겨져 옴. 이런 일은 최대한 지양해야하지만, 이미 개발된 서버를 다 갈아엎자고 할 수도 없음. 인터셉터를 이용할때 장애요인이 되므로 클라이언트 단에서 대응해야함.

관련글 (1)
관련글 (2)
gson parse
stack overflow 참고

        val jsonString = response.peekBody(2048).string()
        val responseJson = JsonParser.parseString(jsonString).asJsonObject
        val resultCode = responseJson.get("resultCode").asInt

안드로이드 드롭다운

드롭다운은 스피너 뷰를 이용함

스피너 가이드 참고, 이 가이드에는 동적으로 드롭다운 메뉴를 추가하는 방법이 나와있지 않지만 함수를 참조해보면 직접 list 를 추가하는 방법도 유효함

스피너는 선택된 값이 있는 상태로 로드 되기때문에 그런 상황을 원하지 않으면 list view 등 다른 view 를 이용해거나. - 관련 질문

조금 복잡하지만 spinner 를 그대로 이용하되, 조금 수정하는 방법이 있다. - 관련 질문

이미지 가져오기

갤러리 앱에서 이미지를 가져오는 것과 파일 중에서 이미지를 가져오는것은 다르다. 카카오에서 사진을 추가할때는 갤러리 앱을 바로 열던데 어떻게 하는걸까?

파일 중에서 이미지 가져오기

파일중(갤러리 사진을 포함하여)에서 이미지를 가져오는 코드는 다음과 같다. 아래는 그 중에서도 여러 이미지를 가져오는 방법이다. 앨범에서 가져오는 것과의 차이는 val intent = Intent(Intent.ACTION_GET_CONTENT) 와 val intent = Intent(Intent.ACTION_PICK) 둘 중 어떤걸 사용하느냐이다. 후자가 이미지를 갤러리에서 가져오는 방법이다.

// activity or fragment

lateinit var resultLauncher: ActivityResultLauncher<Intent>

...

        binding.photoAdd.setOnClickListener{
            val intent = Intent(Intent.ACTION_GET_CONTENT)
            // val intent = Intent(Intent.ACTION_PICK) 갤러리에서 가져오기
            intent.type = "image/*"
            intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
            resultLauncher.launch(Intent.createChooser(intent, "Select Image(s)"))
        }
        
private fun settingResultLauncher(){
        resultLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()) { activityResult ->
            if(activityResult.resultCode == AppCompatActivity.RESULT_OK) {
                val resultData = activityResult.data
                if(resultData?.clipData != null) {
                    val count = resultData.clipData!!.itemCount
                    for (i in 0 until count) {
                        val imageUri = resultData.clipData!!.getItemAt(i).uri
                    }
                }
            }

Uri to Bitmap

글 작성일 기준 이미지 디코더 를 이용하는 것이 추천된다. 이 문서가 너무 설명이 잘 되어 있어서 좋았다.

내가 하려는 작업 기준,

createSource(android.content.ContentResolver, android.net.Uri) 로 soruce 를 만들고, 이를 ImageDecoder.decodeBitmap(android.graphics.ImageDecoder.Source) 에 파라미터로 넘겨줘야한다.

일반적으로 갤러리에서 uri 를 가져온경우 용량이 크기 때문에 압축하는 과정을 거치는데, 이러한 경우를 default setting 을 바꾸는 경우라고 하며, 이때는 ImageDecoder.OnHeaderDecodedListener 를 함께 파라미터로 넘겨줘야한다. kotlin 에서는 당연히 람다로 넘겨줘도 된다.

// over API 28
ImageDecoder.OnHeaderDecodedListener { decoder, info, source -> 
          decoder.setTargetSampleSize(2)
}

이러한 방식은 api 28 이상에서만 유효하다.

safeArgs 로 object list 보내기

https://stackoverflow.com/questions/53520882/safe-args-use-list-of-parcelables

갤러리 앱에서 여러 이미지 가져오기

val intent = Intent()
                intent.type = "*/*"
                val mimeTypes = arrayOf("image/jpeg","image/png")
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
                intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
                intent.action = Intent.ACTION_GET_CONTENT
                resultLauncher.launch(Intent.createChooser(intent, "Select Image(s)"))
                

mimeType 을 jpeg 와 png 로만 제한하였음.

텍스트 글자 코드로 바꾸기

value/color 에 있는 리소스로 할 수 있음.

binding.topicSelected.setTextColor(
    ContextCompat.getColor(
        requireContext(),
        R.color.black_semi)
)

imageview 둥글게 만들기

잘 알려주는 블로그

TextView의 max width 제한하기

https://stackoverflow.com/questions/32032468/how-to-use-wrap-content-with-a-maximum-width/48199856

탭 레이아웃 주의사항

탭 아이템에 id 를 넣으면 안된다.

폰트

폰트 적용시 ttf 보다 otf 포맷을 이용하면 용량이 50% 더 적고, 글씨가 작을때 폰트가 더 이뻐보이는 효과가 있음.

리사이클러뷰 은근 어렵네

메인 쓰레드에서만 list 데이터를 조작해야 예외가 발생하지 않는다.
리사이클러뷰를 리셋할 때 list 를 먼저 건들면 예외가 발생할 수 있다.
remove(0, list.size) 를 먼저 한뒤, insert(n, m) 해주는게 좋다.

시크릿키 보관

res 폴더 하위에 string 형 xml 을 만들고 시크릿키를 보관하는건 안전할까?

locale.properties 에 있는 값을 BuildConfig 에 넣어주는 라이브러리를 이용하거나, 따로 ~.properties 파일을 만들어서 app 수준의 buildscript 에서 BuildFiled 로 넣어주는 방법이 있음.

이때는 반드시 gitignore 에서 해당 파일이 git 에서 관리되지 않도록 해야함.

menifest 에서 secret key 를 이용해야 하는 경우도 있는데, 이럴때도 비슷한 방법을 이용할 수 있음.

에노테이션 프로세스

튜토리얼

에노테이션 프로세스는 java5 부터 지원된 기능임, 컴파일 타임에 새로운 파일 (.java 파일에 국한되지 않음) 을 만드는 역할을 함. 이때 파일을 만드는 것만 가능하고, 수정하는 건 할 수 없음.

duplicate class 오류

앱을 빌드하는 과정에서(나는 테스트를 돌리는 과정에서) Duplicate class 오류를 만났다. 의존하고 있는 라이브러리들 사이에서 동일한 class 이름이 있기 때문에 발생하는 오류이다.

보통 라이브러리 버전을 통일하지 않았거나, 서로 다른 라이브러리가 버전만 다른 동일한 라이브러리에 의존하고 있을때 이러한 오류가 발생한다. 오류 메시지는 아래와 같은 형태로 나온다.
Duplicate class {class name} found in {module1} ({library1}) and {module2} ({library2})

이 문제를 해결하려면 해당 라이브러리에 의존하고 있는 라이브러리를 찾아야하는데, 안드로이드 스튜디오 안에서 찾는 방법이 있는 모양이지만, 나는 CLI 에서 확인하는 방법이 더 쉽게 다가왔다.

(1) 터미널 오픈
(2) 해당 안드로이드 프로젝트까지 이동
(3) ./gradlew :app:dependencies > {filename}.txt
(4) 텍스트 에디터로 해당 파일 열기
(5) 중복이 되는 라이브러리 찾기 (위의 예에서는 org.checkerframework:checker:3.1.1)
(6) gradle 파일에서 exclude 설정하기

나의 경우 텍스트 에디터에서 찾은 결과
androidx.test.espresso:espresso-contrib:3.4.0 -> org.checkerframework:checker:3.1.1
com.google.truth:truth:1.1.3 ->org.checkerframework:checker-qual:3.13.0

두 라이브러리가 충돌하고 있었고..최신 버전을 살려두는게 좋은가? 싶어서 일단 app 수둔 모듈에서 하위 버전을 exclude 해두니까 해결되었다.

META-INF 파일에 따른 빌드 오류

아직 왜인지 모르겠지만 안드로이드 빌드 중 META-INF 파일이 생성될 경우 빌드가 되지 않는 문제가 발생한다.

아래와 같은 오류가 발생했는데 'META-INF/*' 형식으로, 해당 경로 이하에는 여러가지 파일이 생성되는 것 같다.

A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
   > 2 files found with path 'META-INF/AL2.0' from inputs:

친절하게도 도움이 될 수 있는 방법을 알려주는 말이 나오는데, 참조하라는 링크는 유효하지 않기 때문에 Adding a packagingOptions block 라는 키워드로 조사를 했다.

 Adding a packagingOptions block may help, please refer to
     https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
     for more information

app 수준의 android { } 스코프 안에 아래 블럭을 추가해서 해결했다.

   packagingOptions {
        resources.excludes.add("META-INF/*")
    }

Mockito 라이브러리의 missing opentest4j exceptions

Mockito 라이브러리를 이용한 안드로이드 테스트 도중 Failed resolution of: Lorg/opentest4j/AssertionFailedError 에러를 만났다. 한마디로 opentest4j 라이브러리를 찾지 못했다는 것이다. mockito 에서 이용하는 거면 같이 의존성이 추가되어야 하는 것 같은데 뭔가 알 오류가 있는 모양이다. 이와 같은 이슈로 2019년에 이야기 가 있었고, 해결된 모양이지만 지금 2022년에 왜 같은 이슈가 발생했는지는 모르겠다.

하지만 원하는 라이브러리를 못찾았다면 내가 추가해주면 해결되지 않을까 싶어서 나의 app 수준 그레이들에 의존성을 추가해줬다.

testImplementation "org.opentest4j:opentest4j:1.2.0"
androidTestImplementation "org.opentest4j:opentest4j:1.2.0"

추가로 생성되는 META-INF, win32-x86, win32-x86-64 파일들을 exclude 하니 테스트가 문제없이 돌아갔다.

android {
    packagingOptions {
        resources.excludes.add("META-INF/*")
        resources.excludes.add("win32-x86/*")
        resources.excludes.add("win32-x86-64/*")
        resources.excludes.add("META-INF/licenses/ASM")

    }
}

BuildConfig 를 이용하여 api key 를 숨기는 방법은 git 에서만 유효하다

aws 사용자 인증 정보를 locale.gradle 에 넣고 릴리즈한 결과 구글 플레이 콘솔에서 리젝 당했다.

로그인이 필요한 서비스는 배포할때 활성 데모 계정을 같이 제공해야한다.

안그러면 리젝당한다

커뮤니티앱은 신고 기능을 만들지 않으면 앱 릴리즈가 거부 될 수 있다

registerForActivityResult 는 activity 나 fragment 가 started 상태로 들어가기 전에 설정되어야 한다.

onCreate, onCreateView 상태에서 설정되는 것이 좋을듯!

profile
개발자

0개의 댓글