Android 13 Notification Permission 알림권한허용

Android 12 까지는 앱에서 별도의 권한을 받지 않고 Notification 을 띄울 수 있었다. (활성화 상태가 디폴트값)

앱 설정에서 Notification 알림 활성을 통해 Notification 권한을 조정할수 있었다.

하지만, Android 13 부터는 Notificatioin 권한이 기본적으로 비활성 상태이며 유저가 권한을 허용해주어야만 활성화 상태가 된다.

AndroidManifest 에서 Notificatioin 권한을 요청할 수 있다. 

Target SDK API 33 이상에서만 권한 수가가 가능하며, 그 이후는 기존 안드로이드 권한 요청과 동일하다.

<manifest ...> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <application ...> ... </application> </manifest>


Target SDK 33인 앱이 Android 12 이하의 디바이스에 설치된다면?

기존 안드로이드 OS에서는 따로 알림 관련된 권한 요청이 없었다.

POST_NOTIFICATIONS 해당 권한 요청을 아무리 보내도 권한 알림 팝업은 노출되지 않는다.

알림 권한은 기존과 동일하게 설정에 들어가서 활성화/비활성화를 진행하며 기본 앱 진입 시 무조건 활성화 상태로 진입되게 된다.


Target SDK 32인 앱이 Android 13 이상의 디바이스에 설치된다면?

Android 13 부터는 Notification 권한이 존재한다. 그러나 Target SDK 33 이상부터 알림 관련 퍼미션을 정의할 수 있다. (android.permission.POST_NOTIFICATIONS)

notificationChannel을 등록할 때 자동으로 알림 권한 팝업이 노출된다

POST_NOTIFICATIONS (Notification Permission) 은 Target SDK API 33 이상부터 추가 가능

Target SDK API 32 이하의 앱이 Android 13 디바이스에 설치되면 Notification Channel을 등록할 때 자동으로 Notification 권한 요청 팝업이 나옴

Target SDK API 33 이상의 앱이 Android 13 디바이스에 설치되면 Notification 권한요청을 개발자가 원하는 타이밍에 노출 가능

Target SDK API 33 이상의 앱이 Android 12 이하 디바이스에 설치되면 기존과 동일하게 Notification 권한 요청 없이 사용 가능

Target SDK API 32 앱을 33으로 업데이트 시 기존 알림 권한 동의 상태라면 업데이트 이후 기본으로 허용이지만 예외 있음

(기기에따라 자동으로 허용되지 않고 다시 한 번 권한을 얻어야하는 경우가 있음)

//알림권한허용 팝업 (AOS 13부터) private fun postNotificationCheck() { Log.d("postNotificationCheck()") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { TedPermission.with(this) .setPermissionListener(postNotificationListener) .setPermissions(Manifest.permission.POST_NOTIFICATIONS) .check() SharedPref.getInstance().setShowPostNotificationPopup(true) } } private val postNotificationListener: PermissionListener = object : PermissionListener { override fun onPermissionGranted() { Log.d("postNotificationListener onPermissionGranted()") } override fun onPermissionDenied(deniedPermissions: List) { Log.d("postNotificationListener onPermissionDenied()") CommonDialog(this, getString(R.string.common_noti_label), getString(R.string.perm_post_notification_denied_message), false, object : DialogClickListener { override fun onPositiveClick() { //알림설정화면 이동 openAppNotificationSettings(this) } override fun onNegativeClick() { } }) } }


Android 13에서는 Notification 권한 요청을 기존 권한들과 동일한 방식으로 요청하며

Android 12에서는 Notification 권한이 기본적으로 무조건 활성화 상태였다면

Android 13에서는 Notification 권한이 기본적으로 비활성화 상태이며 

사용자가 권한을 허용해줘야 활성화 상태가 됩니다.


그외에 알림권한과 관련된 유용한 정보들은 공식문서를 통해서 확인하실 수 있습니다.

https://developer.android.com/about/versions/13/changes/notification-permission


Android WebView WebViewClient (웹뷰에서 일어나는 요청, 상태, 에러 등 다양한 상황) 재정의 사용

 WebViewClient를 통해 웹뷰에서 일어나는 요청, 상태, 에러 등 다양한 상황에서의 콜백을 조작할 수 있습니다. 

다양한 메소드를 제공하고 있습니다만 대표적으로 사용되는 몇 가지 메소드만 살펴보도록 하겠습니다. 

전체 메소드에 대해서는 developer 사이트에서 확인하실 수 있습니다.


1. shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?)

- 현재 웹뷰에 로드될 URL에 대한 컨트롤을 할 수 있는 메소드


2. onPageStarted(view: WebView?, url: String?, favicon: Bitmap?)

- page loading을 시작했을 때 호출되는 콜백 메소드


3. onPageFinished(view: WebView?, url: String?)

- page loading을 끝냈을 때 호출되는 콜백 메소드


4. onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?)

- request 에 대해 에러가 발생했을 때 호출되는 콜백 메소드. error 변수에 에러에 대한 정보가 담겨져있음


5. onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?)

- 웹서버의 http 에러 발생시 호출되는 콜백 메서드


6. onReceivedSslError(view: WebView?, handler: SslErrorHandler?, error: SslError?)

- 웹뷰 SSL 오류관련 핸들러 콜백 메소드



override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) { super.onReceivedError(view, request, error) Log.e("onReceivedError errorCode : ${error.errorCode}, description : ${error.description}, failingUrl : ${request.url}") val errorCode = error.errorCode //CONNECTION, TIMED_OUT, PROXY 경우 네트워크 에러페이지 노출 //errorCode : -5, description : net::ERROR_PROXY_AUTHENTICATION //errorCode : -6, description : net::ERR_CONNECTION_CLOSED //errorCode : -6, description : net::ERR_CONNECTION_ABORTED, ERR_CONNECTION_FAILED //errorCode : -6, description : net::ERR_CONNECTION_RESET, ERR_CONNECTION_REFUSED //errorCode : -6, description : net::ERR_SOCKET_NOT_CONNECTED //errorCode : -8, description : net::ERR_CONNECTION_TIMED_OUT, net::ERR_TIMED_OUT

    when (errorCode) {

ERROR_AUTHENTICATION -> {} ERROR_BAD_URL -> {} ERROR_CONNECT -> { //errorCode : -6, description : net::ERR_CONNECTION_CLOSED //errorCode : -6, description : net::ERR_CONNECTION_ABORTED //errorCode : -6, description : net::ERR_CONNECTION_FAILED //errorCode : -6, description : net::ERR_CONNECTION_RESET //errorCode : -6, description : net::ERR_CONNECTION_REFUSED //errorCode : -6, description : net::ERR_SOCKET_NOT_CONNECTED } ERROR_FAILED_SSL_HANDSHAKE -> {} ERROR_FILE -> {} ERROR_FILE_NOT_FOUND -> {} ERROR_HOST_LOOKUP -> {} ERROR_IO -> {} ERROR_IO -> {} ERROR_PROXY_AUTHENTICATION -> { //errorCode : -5, description : net::ERROR_PROXY_AUTHENTICATION } ERROR_REDIRECT_LOOP -> {} ERROR_TIMEOUT -> { //errorCode : -8, description : net::ERR_CONNECTION_TIMED_OUT //errorCode : -8, description : net::ERR_TIMED_OUT } ERROR_TOO_MANY_REQUESTS -> {} ERROR_UNKNOWN -> {} ERROR_UNSUPPORTED_AUTH_SCHEME -> {} ERROR_UNSUPPORTED_SCHEME -> {} }

}


override fun onReceivedHttpError( view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) { super.onReceivedHttpError(view, request, errorResponse) Log.d("onReceivedHttpError() url : ${view?.url}, statusCode : ${errorResponse?.statusCode}") // HTTP STATUS CODE 503 (WAS 죽음) // HTTP STATUS CODE 502 (Bad Gateway) try { var statusCode = errorResponse?.statusCode if (statusCode == 503 || statusCode == 502) { //네트워크 에러페이지 노출 } } catch (e: Exception) {} }

Android WebView 타임아웃(TimeOut) 기능

Android WebView는 페이지 관련해서 타이아웃 설정이 없다

상황에 따라 bad network 상황시 WebViewClient에 타이아웃 관련 기능을 통해 사용자에게 네트워크 장애표시를 해야할 필요가 있었다.

다음의 동작으로 간략한 webview에 타임아웃 기능을 구현해 보았다.



mWebView.setWebViewClient(new WebViewClient() {
     private val loadingTimeOut = 1000 * 10
     private var timeout = false


    //페이지 타임아웃 타이머
    private val pageTimeoutTimer: CountDownTimer = object : CountDownTimer(loadingTimeOut.toLong(), 1000) {

        override fun onTick(millisUntilFinished: Long) {
        }

        override fun onFinish() {
            Log.d("pageTimeoutTimer onFinish")


            if (timeout) {
                //네트워크 장애관련 페이지 노출
                Log.d("onPageStarted networkFailActivityForError")

            }

            pageFinishDone()
        }
    }


    //페이지 완료 처리
    private fun pageFinishDone() {
        pageTimeoutTimer.cancel()
        timeout = false

        dismissLoading()
    }


    override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)
        Log.d("onPageStarted WebView URL : $url")

        pageTimeoutTimer.cancel()
        pageTimeoutTimer.start()
        timeout = true

        showLoading()
    }

    override fun onPageFinished(view: WebView?, url: String?) {
        super.onPageFinished(view, url)
        Log.d("onPageFinished WebView URL : $url")

        pageFinishDone()
    }

});

Jetpack Compose Navigation 정리

Navigation 구성요소

Navigation은 크게 3가지 구성요소로 이뤄진다.


NavController: 대상(즉, 앱의 화면) 간 이동을 담당한다. 

NavGraph: 이동할 컴포저블 대상을 매핑을 담당

NavHost: NavGraph의 현재 대상을 표시하는 컨테이너 역할을 하는 컴포저블


NavController

NavController는 Navigation 구성요소의 중심 API로, 스테이트풀(Stateful)이며 앱의 화면과 각 화면 상태를 구성하는 컴포저블의 백 스택을 추적한다. 컴포즈 환경에서 NavController는 rememberNavController()를 이용하여 가져올 수 있다.

val navController = rememberNavController()

rememberNavController()를 호출하여 NavContoller 인스턴스를 생성할 때 유의해야 할 점은 상태 호이스팅(State Hoisting)에 유의해야 한다. 컴포저블 계층 구조에서 NavController를 만드는 위치는 이를 참조해야 하는 모든 컴포저블이 액세스할 수 있는 곳이어야 한다. 이러한 구조가 상태 호이스팅의 원칙을 준수하는 것이다.

왜냐하면 Navigation과 관련된 상태 정보를 스크린과 분리하며, NavController가 생성된 곳에 있는 상태 정보를 여러 스크린이 공유할 수 있기 때문이다.


 

NavGraph

NavGraph는 ID로 가져올 수 있는 NavDestination 노드의 집합이다. NavGraph는 '가상' 대상 역할을 합니다. NavGraph 자체는 백 스택에 나타나지 않지만 NavGraph로 이동하면 시작 대상이 백 스택에 추가된다. 새로운 NavGraph는 대상을 추가하고 시작 경로을 설정할 때까지 유효하지 않다. 대상을 추가하는 방법은 NavGraphBuilder.composable() 함수를 이용한다. 자세한 내용은 아래에서 설명한다.


NavHost

NavController는 하나의 NavHost와 연관되어 있어야 한다. NavHost는 NavController와 Navigation Graph를 연결하는 역할을 한다. 

지정된 NavGraphBuilder 내의 모든 컴포저블을 제공된 navController에서 탐색할 수 있다. 즉 각 NavController를 단일 NavHost 컴포저블과 연결해야 한다. NavHost는 내부적으로 구성 가능한 대상을 지정하는 NavGraph와 NavController를 연결한다. 이 연결을 통해 NavController는 이동한 컴포저블 대상을 파악하고 이동할 수 있게 된다.

화면 전환을 하면서 NavHost 안의 내용은 자동적으로 recompose된다.


public fun NavHost( navController: NavHostController, startDestination: String, modifier: Modifier = Modifier, route: String? = null, builder: NavGraphBuilder.() -> Unit ) NavHost의 원형을 살펴보면, 위와 같다

navController: NavHostController 클래스의 인스턴스이다. navigate() 메서드를 호출하여 다른 대상으로 이동하는 등의 방식으로 화면 간에 이동하는 데 이 객체를 사용할 수 있다.

startDestination: 앱에서 NavHost를 처음 표시할 때 기본적으로 표시되는 대상을 정의하는 문자열 경로이다.

builder: NavGraphBuilder로 그래프를 생성하기 위해 사용되는 빌더이다. 여기에 NavGraphBuilder의 composable() 함수를 이용하여 경로 대상을 정의 할 수 있다.


 

Compose Navigation 적용하기

의존성 추가

 (https://developer.android.com/jetpack/androidx/releases/navigation#declaring_dependencies)

dependencies {
    def nav_version = "2.5.3"
    implementation "androidx.navigation:navigation-compose:$nav_version"
}


경로 추가

앱에서 경로를 추가해야 한다. 경로는 탐색의 기본 개념 중 하나이다. 경로는 Destination에 상응하는 문자열이다. 보통 enum 클래스를 사용하여 경로를 정의하는 편이다.


enum class TestScreen() {
    First,
    Second,
    Third
}


NavController 추가

NavController는 위에서 설명했듯이 상태 호이스팅에 유의하여 rememberNavController() 를 이용하여 선언할 수 있다.


@Composable
fun TestApp(modifier: Modifier = Modifier){
    val navController = rememberNavController()
    ...
}

  

NavHost 추가 및 경로 정의

NavHost를 추가하고 내부에 NavGraphBuilder.composable() 함수를 이용하여 경로를 정의한다. composable은 2개의 매개변수가 필요하다.


route: 경로 이름에 해당하는 문자열입니다. 모든 고유 문자열을 사용할 수 있습니다. CupcakeScreen enum의 상수 이름 속성을 사용합니다.

content: 여기에서 특정 경로에 표시할 컴포저블을 호출할 수 있습니다.


route는 위에서 정의한 경로 (enum class를 사용했었다.)를 이용하여 route 매개변수를 전달하고, content는 컴포저블 함수로 Screen 등 표시할 컨텐츠가 포함된다.

@Composable
fun TestApp(modifier: Modifier = Modifier){
    val navController = rememberNavController()

    ...


    NavHost(
        navController = navController,
        startDestination = TestScreen.First.name,
        modifier = modifier.padding(innerPadding)

    ) {

        composable(route = TestScreen.First.name) {
            FirstScreen(
                ...
            )
        }

        composable(route = TestScreen.Second.name) {
            SecondScreen(
                ...
            )
        }

        composable(route = TestScreen.Third.name) {
            ThirdScreen(
                ...
            )
        }
    }
}
 

NavHost 의 매개변수를 살펴보면, 위에서 생성한 navController를 전달한다. 그리고 처음 시작점인 startDestination으로 TestScreen.First.name을 이용해 첫 경로를 설정하여 준다.

 

경로 간 이동하기 (navigate())

경로 간 이동을 위해 navController의 navigate() 함수를 이용한다. 아래 코드를 보면, FirstScreen의 버튼 클릭 이벤트에서 SecondScreen으로 이동하는 코드이다.

navigate(route)를 통해 destination을 인자로 주어 원하는 화면으로 전환이 가능하다. 이 때, 화면 전환과 함께 back stack에 전환 전의 화면이 추가된다. 다음과 같이 사용한다.


@Composable
fun TestApp(modifier: Modifier = Modifier){

    val navController = rememberNavController()
    ...

    NavHost(
        navController = navController,
        startDestination = TestScreen.First.name,
        modifier = modifier.padding(innerPadding)

    ) {

        composable(route = TestScreen.First.name) {

            FirstScreen(

                ...,

                onNextButtonClicked = {
                    navController.navigate(TestScreen.Second.name)

                }
            )

        }

        composable(route = TestScreen.Second.name) {
            SecondScreen(

                ...

            )
        }
    }

}
  

navController.navigate() 메서드의 옵션들
다양한 옵션들을 이용하여 back stack을 관리할 수 있다. 

- popUpTo(route) 
navigate에 전달한 인자에 해당하는 화면으로 전환하기 전에, route에 해당하는 화면이 나오기 전까지의 back stack을 뺀다. inclusive를 true로 하면 route에 해당하는 화면까지 back stack에서 뺀다. 

- launchSingleTop = true 
route에 해당하는 화면이 아닌 경우에만 route로 화면이 전환된다. back stack에 동일한 route가 중복으로 있는 것을 방지한다.

navController.navigate(item.route) {
    // 항목을 선택할 때 백스택에 대상스택이 많이 생성되는 것을 방지하기 위해 그래프의 시작 대상까지 팝업
    navController.graph.startDestinationRoute?.let { route ->
        popUpTo(route) {
            saveState = true
        }
    }
    
    // 백 스택에 동일한 route가 중복으로 있는 것을 방지
    launchSingleTop = true
    
    // 이전에 선택한 항목을 다시 선택할 때 상태 복원
    restoreState = true
}



navController.popBackStack()
- 이전화면으로 돌아갈때

navController.popBackStack(route, inclusive)
- route: 돌아가고 싶은 화면의 route
- inclusive: Boolean값. true이면 특정한 route를 삭제, false이면 모든 destination을 pop하고 start destination으로 이동

navController.popBackStack(
    TestScreen.First.name,
    false
)


참고

https://developer.android.com/jetpack/compose/navigation

Jetpack Compose 기초정리

 Jetpack Compose 기초는 다음의 링크에서 확인 가능

https://developer.android.com/codelabs/jetpack-compose-basics?hl=ko


*최근 진행중인 개인 프로젝트는 다음의 구조로 진행

MVVM 디자인 패턴(통신 Retrofit) + DI(Dagger Hilt) + Compose

프로젝트를 위해 접해보지 못했던 Compose를 먼저 이해해 보기로 함.



*Compose 정리

**Compose로 Layout을 어떻게 구성하는지 공부


1) 기본적으로 사용되는 레이아웃은 3가지가 있습니다. Column , Row, Box

(단순한 컨테이너로, Compose의 레이아웃을 구성하는데 사용)

- 수직 Column

- 수평 Row

- 중첩 Box



2) 수정자 (Modifier.함수()로 사용 )

- **Modifiers는 순서가 매우 중요함

- Composable의 크기, 레이아웃, 동작 및 모양 변경

- 접근성 라벨과 같은 정보 추가

- 사용자 입력 처리

- UI요소를 클릭 가능, 스크롤 가능, 드래그 가능 또는 확대/축소 가능하게 만드는 것과 같은 높은 수준의 상호작용 추가


Column(modifier = Modifier
    .padding(24.dp)
    .fillMaxWidth()) 
    {
    	Text(text = "Hello,")
    	Text(text = name)
    }


(1) 지정한 크기가 레이아웃의 상위 요소를 무시하고 Composable의 크기를 고정해야 하는 경우 requiredSize 수정자를 사용


Box(Modifier.size(90.dp, 150.dp).background(Color.Green)) {
    Box(Modifier.requiredSize(100.dp, 100.dp).background(Color.Red))
}


    - 가장 하위에 있는  Box가 상위 Box 90.dp 크기를 무시하고 100.dp로 설정

    

(2) 레이아웃의 상위 요소 크기에 상위요소가 허용하는 만큼 하위요소가 꽉 채우고 싶은 경우 

    fillMaxSize(), fillMaxWidth(), fillMaxHeight() 등등을 골라서 사용


Box(Modifier.background(Color.Green).size(50.dp).padding(10.dp)) {
    Box(Modifier.background(Color.Blue).fillMaxSize())
}

    

    -fillMaxSize() 사용으로 상위 UI요소 만큼 하위 UI요소의 크기가 지정됨

    -padding을 넣엇기 때문에 패딩이 10dp가 적용됨 

    

(3) 하위 레이아웃을 상위 요소와 동일한 크기로 설정하려면 matchParentSize 수정자를 사용합니다.   

    Modifier.matchParentSize().background(Color.Green)

    

    

(4) 레이아웃 상단에서 기준선까지 특정 거리가 유지되도록 패딩을 추가하려면 paddingFromBaseline 수정자를 사용


 Box(Modifier.background(Color.Yellow)) {
    Text("Hi there!", Modifier.paddingFromBaseline(top = 32.dp))
}

    

3) Offset    

- 원래 위치를 기준으로 레이아웃을 배치해줌

- offset 수정자를 추가해서 x축 , y축을 설정해서 사용함

- 양수 혹은 음수일 수 있음

- offset의 측정값은 변경되지 않음 

- offset 수정자는 레이아웃 방향에 따라 가로로 적용

- LTR 컨텍스트에서 양수 offset은 요소를 오른쪽으로 이동시킴

- RTL 컨텍스트에서는 요소를 오른쪽으로 이동합니다.

- aboluteOffset은 레이아웃 방향 상관없이 항상 오른쪽으로 이동시킴


Text(
    "Layout offset modifier sample",
    Modifier.offset(x = 15.dp, y = 20.dp)
)

    

4) 반응형 레이아웃

(1) Row 및 Column의 가중치 수정자


Row(Modifier.width(210.dp)) {
    Box(Modifier.weight(2f).height(50.dp).background(Color.Blue))
    Box(Modifier.weight(1f).height(50.dp).background(Color.Red))
}


(2) 제약 조건

- 상위 요소의 제약 조건에 따라 레이아웃을 디자인하려면 BoxWithConstraints를 사용 

- 측정 제약조건은 콘텐츠 람다의 범위에서 확인 가능 

- 측정 제약 조건을 사용하여 다양한 화면 구성에 따라 다양한 레이아웃을 구성이 가능 



5) Surface

일반적으로 카드 또는 패널과 같은 요소를 나타내는데 사용된다. 

그림자, 경계선, 배경색 등과 같은 시각적 효과를 자동으로 처리하며, 표면에 콘텐츠를 배치하는데 사용 (배경색이나 배경 테두리 등을 설정)

- ex) 카드 또는 팝업창을 만들 때, Surface를 사용


Surface(
    color = MaterialTheme.colors.primarySurface,
    border = BorderStroke(1.dp, MaterialTheme.colors.secondary),
    shape = RoundedCornerShape(8.dp),
    elevation = 8.dp
) {
    Text(
        text = "example",
        modifier = Modifier.padding(8.dp)
    )
}


ABC 알파벳 개인정보처리방침

 

개인정보처리방침

ABC 알파벳 어플리케이션은 개인정보보호법에 따라 이용자들의 개인정보 보호 및 권익을 보호하고자 다음과 같은 처리방침을 두고 있습니다.

해당 앱은 아동들이 처음 알파벳을 배울때 발음을 교정하기 위한 앱이며, 어떠한 개인정보도 취급하지 않습니다.

개인정보처리방침을 개정하는 경우 해당 페이지를 통하여 공지할 것입니다.


개인정보의 처리 목적

본 어플리케이션은 개인정보를 수집하지 않는 독립 실행형 어플리케이션으로 별도의 서버를 운영하거나 정보를 수집하지 않습니다.

당사에서 개인정보를 별도로 저장하거나 이용하지 않습니다.


개인정보 파일 현황

당사는 별도의 개인정보 파일을 사용하지 않으며 저장하지 않습니다.


개인정보의 처리 및 보유기간

당사는 개인정보를 직접적으로 저장하거나 보유하지 않습니다.


개인정보처리 위탁

당사는 개인정보를 위탁하고 있지 않습니다.


개인정보의 파기

당사의 어플리케이은 독립 실행 방식의 어플리케이션으로 별도의 서버를 사용하지 않고 있습니다.

또한 개인정보를 저장하지 않으므로 파기할 것이 없습니다.

그러나 사용자가 원할 경우 어플리케이션을 '삭제'함으로서 모든 데이터를 파기할 수 있습니다.


앱 권한

* 앱을 사용하기위한 어떠한 권한도 필요하지 않습니다.


개인정보  보호책임자 작성

이메일 : sunguy119@gmail.com

CleanArchitecture 링크 모음

 https://github.com/DroidKaigi/conference-app-2020

https://github.com/bufferapp/android-clean-architecture-boilerplate
https://github.com/skydoves/DisneyMotions
https://github.com/skydoves/MarvelHeroes
https://github.com/odaridavid/Clean-MVVM-ArchComponents-
https://github.com/android10/Android-CleanArchitecture-Kotlin
https://github.com/BrianSpace/Android-App-Architecture-MVVM-Databinding
https://github.com/igorwojda/android-showcase
https://github.com/arifnadeem7/mvvmcoroutinesandflow
https://github.com/sanogueralorenzo/Android-Kotlin-Clean-Architecture

 

sanogueralorenzo/Android-Kotlin-Clean-Architecture

Android Sample Clean Architecture App written in Kotlin - sanogueralorenzo/Android-Kotlin-Clean-Architecture

github.com

 

arifnadeem7/mvvmcoroutinesandflow

An application which demonstrates usage of Kotlin coroutines and Flow with Android's MVVM architecture. - arifnadeem7/mvvmcoroutinesandflow

github.com

 

igorwojda/android-showcase

💎 Android application following best practices: Kotlin, coroutines, Clean Architecture, feature modules, tests, MVVM, static analysis... - igorwojda/android-showcase

github.com

 

BrianSpace/Android-App-Architecture-MVVM-Databinding

A simple but complete project (in both Java & Kotlin) to demonstrate the Android application architecture with MVVM pattern, a client app for The Movie DB Web API. Dagger2 is used for dependen...

github.com

 

android10/Android-CleanArchitecture-Kotlin

This is a movies sample app in Kotlin, which is part of a serie of blog posts I have written about architecting android application using different approaches. - android10/Android-CleanArchitecture...

github.com

 

skydoves/MarvelHeroes

❤️ A sample Marvel heroes application based on MVVM (ViewModel, Coroutines, LiveData, Room, Repository, Koin) architecture. - skydoves/MarvelHeroes

github.com

 

skydoves/DisneyMotions

🦁 A Disney app using transformation motions based on MVVM (ViewModel, Coroutines, LiveData, Room, Repository, Koin) architecture. - skydoves/DisneyMotions

github.com

 

odaridavid/Clean-MVVM-ArchComponents-

👽 An Android app consuming Star Wars API [RESTful].Built with clean architecture + MVVM pattern, Dagger2 , Coroutines ,Architecture Components, Data Binding , Firebase, Tests and lots more... - oda...

github.com

 

bufferapp/android-clean-architecture-boilerplate

An android boilerplate project using clean architecture - bufferapp/android-clean-architecture-boilerplate

github.com

 

DroidKaigi/conference-app-2020

The Official Conference App for DroidKaigi 2020 Tokyo - DroidKaigi/conference-app-2020

github.com

 

Jetpack Compose 키보드작업처리

사용자가 수정 가능한 텍스트 구성요소(예:  TextField )에 포커스를 맞추면 기기에 하드웨어 키보드가 연결되어 있는 경우 모든 입력은 시스템에 의해 처리됩니다. 주요 이벤트를 처리하여 단축키를 제공할 수 있습니다. 주요 이벤트 Compose에서...