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

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

Yuki Anzai
December 11, 2021

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

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 になった機能は早め早めに対応しよう