Upgrade to Pro — share decks privately, control downloads, hide ads and more …

(Android アプリ開発の)その書き方古いかも。

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Yuki Anzai Yuki Anzai
December 11, 2021

(Android アプリ開発の)その書き方古いかも。

GDG DevFest Tokyo 2021
https://gdg-tokyo.connpass.com/event/229935/

Android を搭載したスマートフォンが世にでてから10年以上が経ち、Androidアプリの開発方法もいろいろと変わってきました。この講演では、ここ数年でやり方が変わったりよりよい方法が提供されたAndroidフレームワークやAndroidXの機能を取り上げ、新しいやり方を紹介します。

Avatar for Yuki Anzai

Yuki Anzai

December 11, 2021
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. 最近のリリース • 2018  android 9 Pie    API Level 28 • 2019  android

    10(Q)  API Level 29 • 2020  android 11(R)  API Level 30 • 2021  android 12(S)  API Level 31
  2. [deprecated] onStateNotSaved() Activity • API Level 28(Android P)から onSaveInstanceState() は

    onStop() の後に呼 ばれるようになった • ↑に伴い、このメソッドは不正確になったので廃⽌
  3. [deprecated] android.preference • 新しい⽅法 • AndroidX の Preference Library (androidx.preference)

    を使う SharedPreferences         → Android フレームワーク (key-value pair を保存する仕組み) 設定画⾯を簡単に作る機能     →  AndroidX (SharedPreferences を        (ライブラリ) 設定値保存に使⽤)
  4. 以前の⽅法 class OldMainActivity : ComponentActivity() { … private fun doSomething()

    { startActivityForResult(Intent(this, DoSomethingActivity::class.java), REQUEST_CODE ) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data ) if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { val id = data?.getStringExtra("id" ) if (id != null) { // .. . } } } }
  5. 新しい⽅法 class MainActivity : ComponentActivity() { … private val doSomethingLauncher

    = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == RESULT_OK) { val id = it.data?.getStringExtra("id" ) if (id != null) { // .. . } } } private fun doSomething() { doSomethingLauncher.launch(Intent(this, DoSomethingActivity::class.java) ) } }
  6. 以前の⽅法 class OldMainActivity : ComponentActivity() { private fun requestCameraPermission() {

    requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE ) } override fun onRequestPermissionsResult ( requestCode: Int , permissions: Array<out String> , grantResults: IntArra y ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults ) if (requestCode == REQUEST_CODE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { doSomethingWithCamera( ) } else { // show messag e } } } … }
  7. 新しい⽅法 class MainActivity : ComponentActivity() { private val requestCameraPermissionLauncher =

    registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted - > if (granted) { doSomethingWithCamera( ) } else { // show messag e } } private fun requestCameraPermission() { requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA ) } … }
  8. 新しい⽅法 class MainActivity : ComponentActivity() { … private val doSomethingIntentSenderLauncher

    = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { if (it.resultCode == RESULT_OK) { val id = it.data?.getStringExtra("id" ) if (id != null) { // .. . } } } private fun doSomething() { doSomethingIntentSenderLauncher.launch ( IntentSenderRequest.Builder ( PendingIntent.getActivity(… ) ).build( ) ) } }
  9. 以前の⽅法 class MainActivity : FragmentActivity() { override fun onAttachFragment(fragment: Fragment)

    { super.onAttachFragment(fragment ) if (fragment is MainFragment) { // do somethin g } } … }
  10. 新しい⽅法 class MainActivity : FragmentActivity() { private val listener =

    FragmentOnAttachListener { fragmentManager, fragment - > if (fragment is MainFragment) { // do somethin g } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState ) setContentView(R.layout.activity_main ) supportFragmentManager.addFragmentOnAttachListener(listener ) } … }
  11. 以前の⽅法 class MainActivity : FragmentActivity() { … override fun onBackPressed()

    { val f = supportFragmentManager.findFragmentById(android.R.id.content ) if (f is EditFragment) { f.onBackPressed( ) } else { super.onBackPressed( ) } } } class EditFragment : Fragment() { … fun onBackPressed() { // show discard confirm dialo g } }
  12. 新しい⽅法 class EditFragment : Fragment() { override fun onViewCreated(view: View,

    savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState ) // activity-kt x val callback = requireActivity().onBackPressedDispatcher.addCallback ( owner = viewLifecycleOwner , enabled = tru e ) { // show discard confirm dialo g } // callback ΛҰ࣌తʹແޮʹ͍ͨ͠ͱ͖͸ callback.isEnabled = fals e } }
  13. FragmentContainerView • 以前の⽅法 • レイアウト XML で <fragment> タグを使う •

    FrameLayout を Fragment の container として使う • 新しい⽅法 • レイアウト XML で <androidx.fragment.app.FragmentContainerView> タグを 使う • FragmentContainerView を Fragment の container として使う
  14. 以前の⽅法 <fragmen t android:id="@+id/main_fragment " android:name="com.sample.myapplication.MainFragment " android:layout_width="match_parent " android:layout_height="match_parent"

    / > <FrameLayou t android:id="@+id/fragment_container " android:layout_width="match_parent " android:layout_height="match_parent" /> supportFragmentManager.beginTransaction( ) .replace(R.id.fragment_container, MainFragment() ) .commit( )
  15. 新しい⽅法 <androidx.fragment.app.FragmentContainerVie w android:id="@+id/main_fragment " android:name="com.sample.myapplication.MainFragment " android:layout_width="match_parent " android:layout_height="match_parent"

    / > <androidx.fragment.app.FragmentContainerVie w android:id="@+id/fragment_container " android:layout_width="match_parent " android:layout_height="match_parent" /> supportFragmentManager.beginTransaction( ) .replace(R.id.fragment_container, MainFragment() ) .commit( )
  16. 新しい⽅法 <androidx.fragment.app.FragmentContainerVie w android:id="@+id/main_fragment " android:name="com.sample.myapplication.MainFragment " android:layout_width="match_parent " android:layout_height="match_parent"

    / > <androidx.fragment.app.FragmentContainerVie w android:id="@+id/fragment_container " android:layout_width="match_parent " android:layout_height="match_parent" /> supportFragmentManager.beginTransaction( ) .replace(R.id.fragment_container, MainFragment() ) .commit( ) • getFragment() で⼀番最後に追加された Fragment を取得できる val fragmentContainerView = findViewById<FragmentContainerView>(R.id.fragment_container ) val f = fragmentContainerView.getFragment<Fragment>()
  17. 以前の⽅法 新しい⽅法 class MainFragment : Fragment(R.layout.fragment_main) { … } class

    MainFragment : Fragment { override fun onCreateView ( inflater: LayoutInflater , container: ViewGroup? , savedInstanceState: Bundle ? ): View = inflater.inflate(R.layout.fragment_main, container, false ) … }
  18. class MainFragment(private val repository: ItemRepository) : Fragment() { … }

    class MainFragmentFactory(private val repository: ItemRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { return when (loadFragmentClass(classLoader, className)) { MainFragment::class.java -> MainFragment(repository ) else -> super.instantiate(classLoader, className ) } } } class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val repository: ItemRepository = … supportFragmentManager.fragmentFactory = MainFragmentFactory(repository ) super.onCreate(savedInstanceState ) … } … } ৽͍͠ํ๏
  19. 新しい⽅法 class MainFragment : Fragment() { private val listener =

    FragmentOnAttachListener { fragmentManager, fragment - > if (fragment is SettingFragment) { // do somethin g } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState ) childFragmentManager.addFragmentOnAttachListener(listener ) } … }
  20. [deprecated] onActivityCreated() (androidx) Fragment • 新しい⽅法 : onViewCreated() を使う •

    Activity の onCreate が呼ばれたときのコールバックが必要な場合は onAttach で LifecycleObserver を登録する
  21. class MainFragment : Fragment() { override fun onAttach(context: Context) {

    super.onAttach(context ) lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { // Activity ͷ onCreate() ͕ݺ͹Εͨͱ͖ʹ΍Γ͍ͨॲཧ } override fun onDestroy(owner: LifecycleOwner) { owner.lifecycle.removeObserver(this ) } } ) } … } • Activity の onCreate が呼ばれたときのコールバックが必要な場合はonAttach で LifecycleObserver を登録する
  22. 以前の⽅法 class OldFragment : Fragment() { private val data =

    MutableLiveData<Result<Data>>( ) override fun onCreate(savedInstanceState: Bundle?) { retainInstance = tru e super.onCreate(savedInstanceState ) lifecycleScope.launch { data.value = … } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState ) data.observe(viewLifecycleOwner) { // show dat a } } }
  23. 新しい⽅法 class NewRetainFragment : Fragment() { private val viewModel: MainViewModel

    by viewModels( ) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState ) viewModel.data.observe(viewLifecycleOwner) { // show dat a } } } class MainViewModel : ViewModel() { private val _data = MutableLiveData<Result<Data>>( ) val data: LiveData<Result<Data> > get() = _dat a init { viewModelScope.launch { _data.value = … } } }
  24. 新しい⽅法 class MainFragment : Fragment() { private val doSomethingLauncher =

    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK) { val id = it.data?.getStringExtra("id" ) if (id != null) { // .. . } } } private fun doSomething() { doSomethingLauncher.launch(Intent(requireContext(), DoSomethingActivity::class.java) ) } … }
  25. [deprecated] getTargetFragment() , setTargetFragment() (androidx) Fragment • 親⼦関係の Fragment 間でデータを受け渡す場合、以前は

    targetFragment を 利⽤していたが deprecated になった • 新しい⽅法 : Fragment Result API をつかう
  26. 以前の⽅法 class ParentFragment : Fragment() { private fun showMyDialogFragment() {

    val f = MyDialogFragment( ) f.setTargetFragment(this, 10 ) f.show(childFragmentManager, "MyDialogFragment" ) } fun onNewValue(value: Int) { // show valu e } } class MyDialogFragment : DialogFragment() { … override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState ) button.setOnClickListener { (targetFragment as? ParentFragment)?.onNewValue(Random.nextInt() ) } } }
  27. 新しい⽅法 class ParentFragment : Fragment() { private fun showMyDialogFragment() {

    childFragmentManager.setFragmentResultListener("ParentFragment", viewLifecycleOwner) { requestKey, bundle - > onNewValue(bundle.getInt("value") ) } val f = MyDialogFragment( ) f.show(childFragmentManager, "MyDialogFragment" ) } private fun onNewValue(value: Int) { // show valu e } } class MyDialogFragment : DialogFragment() { … override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState ) button.setOnClickListener { setFragmentResult("MyDialogFragment", bundleOf("value" to Random.nextInt()) ) } } } 同じ FragmentManager 内の Fragment 間、 親⼦関係の Fragment 間 で使える
  28. 以前の⽅法 class OldViewPagerActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState ) val viewPager = ViewPager(this).apply { id = R.id.view_page r } setContentView(viewPager ) viewPager.adapter = MyPagerAdapter(supportFragmentManager ) } } class MyPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) { override fun getCount(): Int { return 1 0 } override fun getItem(position: Int): Fragment { return … } }
  29. 新しい⽅法 class NewViewPagerActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState ) val viewPager = ViewPager2(this).apply { id = R.id.view_page r } setContentView(viewPager ) viewPager.adapter = MyPagerAdapter(this ) } } class MyPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { override fun getItemCount(): Int { return 1 0 } override fun createFragment(position: Int): Fragment { return … } }
  30. deprecated になった Fragment 機能の利⽤を検出する • StrictMode for Fragment (1.4.0 〜)

    class ExampleActivity : AppCompatActivity() { init { supportFragmentManager.strictModePolicy = FragmentStrictMode.Policy.Builder( ) .penaltyDeath( ) .detectRetainInstanceUsage( ) .allowViolation(ThirdPartyFragment::class.java, RetainInstanceUsageViolation::class.java ) .build( ) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState ) // .. . } } https://developer.android.com/guide/fragments/debugging?hl=en#strictmode
  31. detectFragmentReuse() FragmentManager ͔Β remove ͞Εͨ͋ͱͷ Fragment Λ࠶ར༻͍ͯ͠Δ͔ detectFragmentTagUsage() ϨΠΞ΢τXML Ͱ

    <fragment> λάΛ࢖͍ͬͯΔ͔ detectRetainInstanceUsage() setRetainInstance() / getRetainInstance() ΛݺΜͰ͍Δ͔ detectSetUserVisibleHint() setUserVisibleHint() ΛݺΜͰ͍Δ͔ detectTargetFragmentUsage() setTargetFragment() / getTargetFragment() / getTargetRequestCode() ΛݺΜͰ͍Δ͔ detectWrongFragmentContainer() FragmentContainerView Ҏ֎ͷ View ʹ Fragment ͕௥Ճ͞Ε͍ͯΔ͔ 検出できる違反の種類
  32. 以前の⽅法 class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState ) lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME ) fun doSomething(owner: LifecycleOwner) { // do somethin g } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY ) fun onDestroy(owner: LifecycleOwner) { owner.lifecycle.removeObserver(this ) } } ) } … }
  33. 新しい⽅法 DefaultLifecycleObserver class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState ) lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { // do somethin g } override fun onDestroy(owner: LifecycleOwner) { owner.lifecycle.removeObserver(this ) } } ) } … } Lifecycle.Event.ON_RESUME Lifecycle.Event.ON_DESTROY
  34. 新しい⽅法 LifecycleEventObserver class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState ) lifecycle.addObserver(object : LifecycleEventObserver { override fun onStateChanged(owner: LifecycleOwner, event: Lifecycle.Event) { when (event) { Lifecycle.Event.ON_RESUME -> { // do somethin g } Lifecycle.Event.ON_DESTROY -> { owner.lifecycle.removeObserver(this ) } } } } ) } … }
  35. まとめ • startActivityForResult() や requestPermission() には ActivityResultContract を使う • Fragment

    は書き⽅がいろいろ変わっている • Fragment ⽤の StrictMode で違反を簡単に検出できる • deprecated になった機能は早め早めに対応しよう