Android Kotlin Fundamentals는 codelab에 올라와있는 강의를 한글로 번역한 내용입니다.
코드는 github를 참고해주세요
03-3. ExternalActivity
1. Set up and use the safe Args plugin
- 사용자가 앱 내에서 데이터를 공유하려면 하나의 Fragment에서 다른 Fragment로 parameter를 전달해야 한다.
- 이러한 트랜잭션 안에서 버그를 예방하고 형식을 안전하게 유지하려면 Gradle 플러그인인 Safe Args를 사용해라.
- 플러그인은 NavDirection 클래스를 생성하는데 이 클래스를 코드에 추가한다.
- 이번 실습에서는 NavDirection 클래스를 사용하여 fragment 간에 argument를 전달한다.
Why you need the Safe Args Plugin
-
앱은 종종 프래그먼트끼리 데이터를 주고 받기를 원한다. 데이터를 전달하는 방법 중 하나는 Bundle 객체를 사용하는 것인데, 안드로이드 Bundle은 key-value 저장소이다
-
키-밸류 저장소는 고유키(문자열)를 사용하여 해당 키와 연관된 값을 가져오는 데이터 구조이다
-
Bundle을 사용하여 fragmentA에서 fragmentB로 데이터를 전달할 수 있는데, fragmentA에서 key-value 쌍의 데이터를 저장한 Bundle을 만들면 fragmentB에서 Bundle 객체로부터 key-value 데이터를 얻을 수 있다
-
그러나 Bundle은 코드가 컴파일 되더라도 앱이 실행 될 때 오류가 발생할 가능성이 있다
-
발생할 수 있는 오류는 아래와 같다
- type mis-match error : 만약 A 프래그먼트가 string으로 보내고 B 프래그먼트가 bundle에서 integer로 요청할 경우, 해당 요청은 default 값으로 0를 리턴한다. 0이 유효한 값이므로 앱이 컴파일 될 때는 mis-match type 문제가 발생하지 않지만 앱이 실행될 때 오류로 인하여 앱이 잘못 작동되거나 중단될 수 있다
- Missing key errors: B 프래그먼트에서 bundle에 저장되어 있지 않은 argument를 요청할 경우 null을 리턴해준다. 앱을 컴파일 할 때 오류가 발생하지 않더라도 사용자가 앱을 실행할 때 문제가 발생할 수 있다
-
당연한 이야기지만 productio에 배포하기 전에 에러를 잡기 위해 컴파일 시점에서 에러를 발견하고 싶을 것이다.
-
이를 위해 안드로이드 Navigation Architecture Component애는 Safe Args 기능이 있다
-
Safe Args는 컴파일 시점에서 에러를 발견하는 코드나 클래스를 생성해주는 Gradle plugin이다
Step 1: Add Safe Args to the project
1) 프로젝트 레벨 build.gradle 파일을 열어서 navigation-safe-args-gradle-plugin 디펜던시를 추가한다
navigationVersion은 File > Project Structure > Variable에서 확인 가능
// project-level build.gradle
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
}
2) app 레벨 build.gradle에 apply plugin 문장을 추가한다
apply plugin: 'androidx.navigation.safeargs'
3) 프로젝트를 rebuild 하면 앱 프로젝트에는 이제 생성된 NavDirection 클래스가 포함된다
- Safe Args 플러그인은 각 fragment에 대해 NavDirection 클래스를 생성한다
- NavDirection 클래스는 앱의 모든 action에 대해 navigation을 나타낸다
- 예를 들어 GameFragment는 GameFragmentDirection 클래스가 생성되고, GameFragmentDirection 클래스로 type-safe한 argument를 game fragment에서 다른 fragment에 전달할 수 있다
- generated 된 파일을 보려면 generatedJava 폴더를 참고한다.
Step2: Add a NavDirection class to the gameFragment
이 단계에서는 GameFragmentDirections 클래스를 gameFragment에 추가한다. 나중에 이 코드를 사용하여 GameFragment와 game-state fragments(GameWonFragment, GameOverFragment)간에 인수를 전달한다.
1) GameFragment.kt를 열어서 onCreateView() 메소드 내에 NavController.navigate() 메소드의 파라미터를 변경한다.
2) gameWonFragment의 action ID를 GameFragmentDirections 클래스의 actionGameFragmentToGameWonFragment() 메소드를 사용하는 action ID로 바꾼다
// Using directions to navigate to the GameWonFragment
view.findNavController()
.navigate(GameFragmentDirections.actionGameFragmentToGameWonFragment())
3) 위와 같이 gameOverFragment도 action ID를 GameFragmentDirections 클래스의 game over 메소드를 사용하는 ID로 바꾼다.
// Using directions to navigate to the GameOverFragment
view.findNavController()
.navigate(GameFragmentDirections.actionGameFragmentToGameOverFragment())
2. Add and pass arguments
gameWonFragment에서 gameFragmentDirection의 메소드로 argument를 전달한다.
Step1: Add arguments to the game-won Fragment
1) navigation.xml에서 Design탭을 눌러 navigation graph를 연다.
2) preview에서 gameWonFragment를 선택한다.
3) Attributes 창에서 Argument 영역을 확대하고 + 아이콘을 눌러 name이 numQuestions이고 type이 Integer인 argument를 추가한다
4) 마찬가지로 두번째 argument로 name이 numCorrect이고 타입이 Integer인 argument를 추가한다.
- 이 상태에서 app을 빌드할 경우 compile error가 난다. No value passed for parameter 'numQuestions' No value passed for parameter 'numCount'
Step2: Pass the Argument
이번 단계에서는 numQuetions과 questionIndex 인수를 GameFragmentDirections 클래스의 actionGameFragmentToGameWonFragment()로 전달한다.
1) GameFragment.kt를 열어 아래와 같이 numQuestions와 questionIndex 인수를 추가한다
// before
view.findNavController()
.navigate(GameFragmentDirections
.actionGameFragmentToGameWonFragment())
// after
view.findNavController()
.navigate(GameFragmentDirections
.actionGameFragmentToGameWonFragment(numQuestions, questionIndex))
2) GameWonFragment.kt에서 Bundle 객체로부터 argument들을 추출한다.
val args = GameWonFragmentArgs.fromBundle(requireArguments())
Toast.makeText(context, "NumCorrect: ${args.numCorrect},
NumQuestions: ${args.numQuestions}", Toast.LENGTH_LONG).show()
Step 3: Replace fragment classes with NavDirection classes
-
"safe arguments"를 사용하려면 fragment 클래스를 NavDirection 클래스를 이용하여 변경해야 한다.
-
이제 다른 fragment에 type-safe arguments를 사용할 수 있다
-
TitleFragment, GameOverFragment, GameWonFragment의 navigate() 메소드에서 전달하는 action id를 NavDirection 클래스의 action id로 변경한다.
1) TitleFragment.kt를 열어서 onCreateView() 내에 위치한 navigate() 메소드의 action id를 아래와 같이 바꾼다.
// view.findNavController().navigate(R.id.action_titleFragment_to_gameFragment) // before
view.findNavController().navigate(TitleFragmentDirections.actionTitleFragmentToGameFragment())
2) GameOverFragment.kt의 Try Again과 GameWonFragment의 Next Match 버튼 클릭 핸들러의 action id도 동일하게 변경한다.
3. Add an Implicit Intent and a 'share' menu item
GameWonFragment 클래스 내에 options menu를 공유 기능 메뉴로 구현해본다.
Implicit Intents
- 지금까지는 네비게이션 구성 요소를 사용하여 activity 내 fragment 간의 이동을 구현했다
- 안드로이드는 intent를 사용하여 다른 앱에서 제공하는 activity로 이동을 허용한다.
- 이번 예제에서는 사용자가 게임 플레이 결과를 공유할 수 있는 기능을 추가한다.
- Intent는 android component간 커뮤니케이션을 위해 사용되는 메세지 객체이다.
- Intent는 explicit와 implicit라는 두 가지 타입이 있다. explitcit intent를 사용하여 정확한 target에 메세지를 전달할 수 있고, implicit intent를 사용하여 작업을 처리할 앱이나 activity를 알지 못해도 activity를 시작할 수 있다.
- implicit intent의 대표적인 예로는 카메라 앱이 있다.
- 예를 들어 사진앱의 경우 다수의 앱이 같은 implicit intent를 처리할 수 있다. 안드로이드는 사용자에게 chooser를 보여주며, 사용자는 요청을 처리할 앱을 선택한다
- 각각의 implicit intent는 수행할 작업의 유형을 설명하는 ACTION을 가지고 있어야 된다. 보통 action에는 ACTION_VIEW, ACTION_EDIT, ACTION_DIAL 등이 있다.
- Intent의 action은 앱의 navigation graph에서 사용하는 action과 전혀 관련이 없다.
Step 1: Add an options menu to the Congratulations Screen
- GameWonFragment.kt 파일을 연다
- onCreateView() 메소드에서 return 문 전에 setHasOptionsMenu(true)를 설정한다
setHasOptionsMenu(true)
Step 2: Build and call an implicit intent
- 사용자의 게임 데이터를 전달하는 intent를 만드는 코드를 추가한다.
- 여러 앱이 ACTION_SEND 인텐트를 처리할 수 있으므로 chooser가 사용자에게 띄어질 것이다.
1) GameWonFragment의 onCreateView()에서 ACTION_SEND 인텐트로 공유할 메세제를 전달할 수 있다.
- ACTION_SEND 인텐트를 통해 사용자가 공유하려는 메세지를 전달할 수 있고, 데이터의 유형은 setType() 메소드로 지정된다. 실제 전달할 데이터는 EXTRA_TEXT에 지정된다.
// Creating our Share Intent
private fun getShareIntent(): Intent {
val args = GameWonFragmentArgs.fromBundle(requireArguments())
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.setType("text/plain").putExtra(Intent.EXTRA_TEXT,
getString(R.string.share_success_text, args.numCorrect, args.numQuestions))
return shareIntent
}
2) startActivity()를 호출하여 intent를 얻는 메소드를 만든다
private fun shareSuccess(){
startActivity(getShareIntent())
}
3) onCreateOptionsMenu()를 오버라이드 하여 winner_menu에 inflate 시킨다.
-
getShareIntent()를 사용하여 shareIntent를 얻는다.
-
ShareIntent가 Activity로 변환(resolve)되는지 확인하기 위해 Android package manager를 사용한다.
-
Android Package Manager는 기기에 설치된 앱 및 액티비티들을 추적한다.
-
resolveActivity()가 null 이면 shareIntent가 resolve 되지 않은 것이므로, sharing menu를 invisible 시킨다
// Showing the Share Menu Item Dynamically
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.winner_menu, menu)
if(getShareIntent().resolveActivity(requireActivity().packageManager)==null){
menu.findItem(R.id.share).isVisible = false
}
}
4) menu item을 처리하기 위해 onOptionsItemSelected() 메소드를 오버라이드 한다. share menu가 눌릴 경우 shareSuccess()를 호출하는 메소드를 추가한다
// Sharing from the Menu
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.share -> shareSuccess()
}
return super.onOptionsItemSelected(item)
}
'android > android kotlin fundamentals' 카테고리의 다른 글
04-2. Complex LIfecycle (0) | 2021.02.17 |
---|---|
04-1. Lifecycles and Logging (0) | 2021.02.16 |
03-2. Navigation (0) | 2021.02.16 |
03-1. Fragment (0) | 2021.02.16 |
02-4. Data Binding (0) | 2021.02.16 |
댓글