android library를 만들고 배포할려면 maven repository가 필요한데, 이게 준비가 되어 있지 않다면 aar파일을 직접 전달해야 하는 번거러움이 있습니다. 그런데 github packages에 apache maven을 지원해주기에 간단하게 aar library를 배포하여 사용할 수 있습니다.

github packages에 대한 설명은. 해당 링크로..

github - personal access tokne 생성

github의 자신의 계정에서 Settings-> Developr settings -> Personal access tokens 으로 가서 access token을 생성 합니다.
write packages, read packages 를 체크 합니다.

properties

project의 root 위칭에 github.properties 를 만들고 그 안에
github에서의 username과 access token생성한 것을 적용해 줍니다.
그리고 해당 파일은 github에 올라가지 않도록 .gitignore 에 추가해 주는 것이 좋습니다. 보안상...

github_username={username}
github_access_token={access_token}

Publish script

library module에 있는 위치에 publish.gradle 를 만듭니다.
그리고 그 안의 내용에는

apply plugin: 'maven-publish'

def githubProperties = new Properties()
githubProperties.load(new FileInputStream(rootProject.file("github.properties")))

def LIB_GROUP_ID = {library_group_id}
def LIB_ARTIFACT_ID = {artifact_id}
def LIB_VERSION = {version_name}

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier "sources"
}

publishing {
    repositories {
        maven {
            name = 'GithubPackages'
            url = uri("https://maven.pkg.github.com/{user_name}/{project_name}")
            credentials {
                username = githubProperties['github_username'] ?: System.getenv("github_username")
                password = githubProperties['github_access_token'] ?: System.getenv("github_access_token")
            }
        }
        maven {
            name = 'CustomMavenRepo'
            url = "file://${buildDir}/repo"
        }
    }
    publications {
        deploy(MavenPublication) {
            groupId LIB_GROUP_ID
            artifactId LIB_ARTIFACT_ID
            version LIB_VERSION
            artifact("$buildDir/outputs/aar/${aar_library_name}.aar")
            artifact(sourceJar)

            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')

                //Iterate over the compile dependencies (we don't want the test ones), adding a <dependency> node for each
                configurations.api.allDependencies.each {
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', it.group)
                    dependencyNode.appendNode('artifactId', it.name)
                    dependencyNode.appendNode('version', it.version)
                }
            }
        }
    }
}

그리고 나서, library project에 있는 build.gradle 에 아래와 같이 추가해 줍니다.

apply from: file('publish.gradle')

이와 같이 추가하고 나면

그림과 같이 gradle에 publishing 에 대한 부분이 생깁니다.

library를 build후 publishDeployPublicationToCustomMavenRepoRepository 하고 나면, local repo에 배포가 되는 부분을 확인할 수 있습니다.

실질적으로 배포를 할려면 publishDeployPublicationToGithubPackagesRepository 로 하면 github의 지정된 username/project의github packages에 배포가 되게 됩니다.

library 사용

root에 있는 build.gradle 에 다음과 같이 repositories에 추가해 줍니다.

def githubProperties = new Properties()
githubProperties.load(new FileInputStream(rootProject.file("github.properties")))

repositories {
        google()
        jcenter()

        mavenLocal()
        maven {
            name = {maven_repository_name}
            url = uri("https://maven.pkg.github.com/{user_name}/{project_name}")
            credentials {
                username = githubProperties['github_username'] ?: System.getenv("github_username")
                password = githubProperties['github_access_token'] ?: System.getenv("github_access_token")
            }
        }
    }

그리고 application 에 있는 build.gradle 에 maven repository에 있는 library 사용할 때 처럼.

implementation '{library_group_id}:{artifact_id}:{version}'

으로 해서 사용하면 됩니다.

maven repository를 만들고 관리하는 것 또한 비용인데, 이걸 github packages를 이용한다면 간단히 해결되는 것 같습니다.

하나의 RecylerView에서 다양한 viewholder화면을 보여줄려면, 이전에는 하나의  adapter안에서 type들을 지정하고, 해당 position에 대해서 type에 맞는 viewholder들을 가져와서 보여줬습니다.

하지만, 여러 type들이 존재하거나 할 경우는 adapter안에는 다양한 type과 viewholder내용들이 존재하게 됩니다.

header,. 그리고 item들이 순차적으로 있다고 해도, adapter에 대한 position의 계산도 틀려집니다.

그래서 이번에 concatadapter를 볼려고 합니다.

developer.android.com/reference/androidx/recyclerview/widget/ConcatAdapter

 

ConcatAdapter  |  Android 개발자  |  Android Developers

ConcatAdapter public final class ConcatAdapter extends Adapter An RecyclerView.Adapter implementation that presents the contents of multiple adapters in sequence. MyAdapter adapter1 = ...; AnotherAdapter adapter2 = ...; ConcatAdapter concatenated = new Con

developer.android.com

다양한 adapter들을 결합에서 하나의 recyclerview에서 내용을 보여줄 수 있습니다.

header, item, footer 가 존재한다면,

headeradapter, itemadapter, footeradapter를 만들고, 그걸 concatadapter에 결합해서 사용하면 됩니다.

그리고 notify할 경우도, 각 adapter에서 따로 진행할 수도 있습니다.

간단하게 만든 sample은.

github.com/drcarter/ConcatAdapterTest

 

drcarter/ConcatAdapterTest

Contribute to drcarter/ConcatAdapterTest development by creating an account on GitHub.

github.com

으로 간단히 만들어 봤습니다.

 

Assert   : 9777A9
Debug    : 6A98B9
Error    : FF6B68
Info     : 6A855A
Verbose  : BBBBBB
Warning  : BC7739

이정도 색상으로 변경해서 사용하면,,.

프로젝트의 시간이 길어질 수록 사용하지 않는 resource가 많아지는 문제가 있습니다.

리펙토링 하면서 이전 리소스에 대해서 지울 수도 있지만, 명시적으로 지워준다면 id를 만들지 않기 때문에 더 좋을 수도 있죠.

build.gradle 에서

android {
    buildTypes {
        release {
            shrinkResources true
        }
    }
}

으로 빌드시에 resource정리를 하는 방법도 있지만, 명시적으로 지우는 것을 해볼까 합니다.

android studio 에서 Refactor -> Remove Unused Resources... 라는 메뉴가 있습니다.

해당 기능을 통해서 layout, string, drawable, color, dimen 등등 사용하지 않는 resource를 지울 수 있습니다.

단, 문제가 하나 있는데,

dynamic resource로 접근하는 부분에 대해서는 걸러주지 않습니다.

그 부분을 해결하기 위해서는

resource 유지를 위해서 keep 처리를 해야 하는데, tools:keep 을 적용하면 됩니다.

예를 들어서, resource.getIdentifiericon_position_의 prefix로 되는 resource들을 다 유지하고 싶다면,

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" 
    tools:keep="@drawable/icon_position_*">
</resources>

으로 xml resouce파일을 만들어서 관리하면 됩니다. 이 방법은 shrinkResources 로 dynamic resource에 접근하는 것들에 대한 유지 방법과 같습니다.

Firebase Crashlytics에서 custom UnCaughtExceptionHandler를 적용하기 위해선 초기화 하는 방법을 변경해 줘야 합니다.

Fabric에서 하는 방법은 별다른 방법이 없어도 잘 동작했지만, fabric에서 firebase crashlytics로 넘어가면서 초기화 순서가 중요해 졌습니다.

firebase의 초기화는 contentprovider를 통해서 초기화 됩니다.

contentprovider의 속성중 순서를 정할 수 있는 부분이, android:initOrder입니다. 이에 대한 설명은

동일한 프로세스에서 호스팅하는 다른 콘텐츠 제공자에 상대적으로 콘텐츠 제공자를 인스턴스화해야 하는 순서입니다. 콘텐츠 제공자 사이에 종속성이 있는 경우 제공자별로 이 속성을 설정하면 종속성에서 요구하는 순서대로 생성됩니다. 값은 단순 정수이며 숫자가 높을수록 먼저 초기화됩니다.

으로 되어 있습니다.
custom exception handler가 firebase 보다 먼저 초기화 되어야 하는데, 그렇지 않아서 정상적으로 동작하지 않는 부분입니다.

그럼 해결방법은.
custom exception handler를 초기화 하는 conentprovider를 등록하고 android:initOrder 의 값을 firebase보다 크게 지정하면 됩니다. firebase 의 initOrder값은 100으로 되어 있습니다.

override fun onCreate(): Boolean {
    val myUncaughtExceptionHandler = UncaughtExceptionHandler(
        Thread.getDefaultUncaughtExceptionHandler()
    )
    Thread.setDefaultUncaughtExceptionHandler(myUncaughtExceptionHandler)

    return true
}
<provider
    android:name=".UncaughtExceptionHandlerContentProvider"
    android:authorities="${applicationId}"
    android:exported="false"
    android:grantUriPermissions="false"
    android:initOrder="101" />

이와 같이 manifest 에 등록하고 사용하면 됩니다.

android개발하다 보면 onActivityResult로 결과를 받아오는 경우가 많습니다.

A activity에서 B activity로 startActivityForResult 로 호출 후, B activity에서의 결과를 onActivityResult로 받게 되죠.

그런데, 

implementation 'androidx.activity:activity-ktx:1.2.0-alpha04'
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha04'

에서 다른 기능이 생겼습니다.

호출과 동시에 lambda function으로 다로 onActivityResult로 결과를 받지 않아도 됩니다.

 

일반적인 방법으로는

Intent(this@MainActivity, SubActivity::class.java).run {
                            startActivityForResult(this, REQUEST_CODE_SUBACTIVITY)
}

와 같은 방법으로 호출하면,

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    when (requestCode) {
        REQUEST_CODE_SUBACTIVITY -> {
            if (resultCode == Activity.RESULT_OK) {
                val resultValue = data?.getStringExtra("result") ?: ""
                binding.textCallback.text = resultValue + " Normal"
            }
        }
    }
 }

와 같이 결과를 받습니다.

 

그런데, activity-ktx:1.2.0-alpha, fragment-ktx:1.3.0-alpha에 추가된 방법으로 한다면

registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
                    when (it.resultCode) {
                        Activity.RESULT_OK -> {
                            val resultValue = it.data?.getStringExtra("result") ?: ""
                            textCallback.text = resultValue + " KTX"
                        }
                        Activity.RESULT_CANCELED -> {
                        }
                    }
                }.launch(
                        Intent(this@MainActivity, SubActivity::class.java)
                )

와 같이 진행할 수 있습니다.

onActivityResult의 override된 mehtod로 결과를 받지 않아도... 간결화된 코드로 가독성이 좋아졌다고 볼 수 있습니다.

 

StartActvityForResult이외에

StartActivityForResult
StartIntentSenderForResult
RequestMultiplePermissions
RequestPermission
TakePicturePreview
TakePicture
TakeVideo
PickContact
GetContent
...

 

 

kotlin으로 파일을 읽는 방법은 java로 할 하는 경우보다는 간단?? 한듯 합니다.

최종적으로 android하면서 사용한 방법은

fun fileRead(context: Context, fileUri: Uri): String {
        val sb = StringBuilder()
        val inputStream = context.contentResolver.openInputStream(fileUri)
        inputStream?.let {
            val reader = BufferedReader(InputStreamReader(inputStream))
            val readLines = reader.readLines()
            readLines.forEach {
                sb.append(it)
            }
            
            it.close()
        }
        return sb.toString()
    }

이지만 아래와 같은 방법들이 있습니다.

1. forEachLine

fun fileReadforEachLine(file: String): String {

        val sb = StringBuilder()

        File(file).forEachLine {
            sb.append(it)
        }

        return sb.toString()
    }

2. useLines

fun fileReadUseLines(file: String): String {
        val sb = StringBuilder()

        File(file).useLines {
            it.forEach { str ->
                sb.append(str)
            }
        }

        return sb.toString()
    }

3. bufferedReader

fun fileReadBufferedReader(file: String): String {
        val sb = StringBuilder()

        File(file).bufferedReader().readLine().forEach {
            sb.append(it)
        }

        return sb.toString()
    }

4. readLines

fun fileReadReadLines(file: String): String {
        val sb = StringBuilder()

        File(file).readLines().forEach {
            sb.append(it)
        }

        return sb.toString()
    }

5. inputStream

fun fileReadInputStream(file: String): String {
        return File(file)
            .inputStream()
            .readBytes()
            .toString(Charsets.UTF_8)
    }

6. readText

fun fileReadReadText(file: String): String {
        return File(file).readText(Charsets.UTF_8)
    }

android Pie 대응을 위해서 targetSdkVersion을 28로 변경 후  갑자기

UnknownServiceException: CLEARTEXT communication to example.com not permitted by network security policy

와 같은 오류가 나오게 경험을 하게 됩니다.


이유는 android pie부터 http가 아닌 https를 이용해야 합니다.



의 내용을 확인하면 됩니다.


간단하게 api의 도메인이 http가 아닌 https를 지원하고 http를 https로 변경만 하면 됩니다. 대부분의 회사 api들이 https를 지원하지만 그렇지 않은 경우도 있죠.

이럴 땐 http로 통신할 도메인들에 대해서 관리를 해주면 됩니다.


우선


res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
   
<domain-config>
       
<domain includeSubdomains="true">example.com</domain>
       
<trust-anchors>
           
<certificates src="@raw/my_ca"/>
       
</trust-anchors>
   
</domain-config>
</network-security-config>


와 같은 파일을 만들어 준 뒤

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
   
<application android:networkSecurityConfig="@xml/network_security_config"
                    ...
>
        ...
   
</application>
</manifest>

와 같이 androidmanifest에 등록해 줍니다.


다른 방법은.

<base-config cleartextTrafficPermitted="true" />

을 network_security_config.xml에 등록을 해서 기본적으로 http를 지원하게 하는 방법도 있습니다. 이건 지정된 domain이외에 기본적으로.. 외부의 api 에 대해서 http를 지원하겠다고 하는 것입니다.


또 다른 방법으로는

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
   
<application android:useCleartextTraffic="true"
                    ...
>
        ...
   
</application>
</manifest>

해주는 것입니다.

자세한 설명은

android:usesCleartextTraffic


링크를 통해서 확인할 수 있습니다.


그래도.. 왠만하면 api는 https를 지원하는게 좋겠죠!!!!

'Android' 카테고리의 다른 글

goodbye? onActivityResult...  (0) 2020.05.14
kotlin file read...  (0) 2019.04.17
AndroidStudio에서 File Template이용하기.  (0) 2016.06.14
AndroidStudio에서 lombok 사용하기  (0) 2016.04.10
Dagger2 + Sqlbrite  (0) 2015.11.17

+ Recent posts