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. DevFest Tokyo 2021
    Yuki Anzai(@yanzm) 
    GDE for Android 
    その書き⽅古いかも。

    View Slide

  2. 最近のリリース
    • 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

    View Slide

  3. Android Framework

    View Slide

  4. Activity

    View Slide

  5. [deprecated] onStateNotSaved()
    Activity
    • API Level 28(Android P)から onSaveInstanceState() は onStop() の後に呼
    ばれるようになった
    • ↑に伴い、このメソッドは不正確になったので廃⽌

    View Slide

  6. android.preference

    View Slide

  7. [deprecated] android.preference
    • 新しい⽅法
    • AndroidX の Preference Library (androidx.preference) を使う

    View Slide

  8. [deprecated] android.preference
    • 新しい⽅法
    • AndroidX の Preference Library (androidx.preference) を使う
    SharedPreferences         → Android フレームワーク
    (key-value pair を保存する仕組み)
    設定画⾯を簡単に作る機能     →  AndroidX
    (SharedPreferences を        (ライブラリ)
    設定値保存に使⽤)

    View Slide

  9. AndroidX

    View Slide

  10. ComponentActivity
    FragmentActivity

    View Slide

  11. 継承関係
    androidx
    • ComponentActivity
    • FragmentActivity
    • AppCompatActivity

    View Slide

  12. [deprecated] startActivityForResult(), onActivityResult()
    ComponentActivity
    • 新しい⽅法 : ActivityResultContract を使う

    View Slide

  13. 以前の⽅法
    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)
    {

    // ..
    .

    }

    }

    }

    }

    View Slide

  14. 新しい⽅法
    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)
    )

    }

    }

    View Slide

  15. [deprecated] onRequestPermissionsResult()
    ComponentActivity
    • 新しい⽅法 : ActivityResultContract を使う

    View Slide

  16. 以前の⽅法
    class OldMainActivity : ComponentActivity()
    {

    private fun requestCameraPermission()
    {

    requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE
    )

    }

    override fun onRequestPermissionsResult
    (

    requestCode: Int
    ,

    permissions: Array
    ,

    grantResults: IntArra
    y

    )
    {

    super.onRequestPermissionsResult(requestCode, permissions, grantResults
    )

    if (requestCode == REQUEST_CODE)
    {

    if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
    {

    doSomethingWithCamera(
    )

    } else
    {

    // show messag
    e

    }

    }

    }


    }

    View Slide

  17. 新しい⽅法
    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
    )

    }


    }

    View Slide

  18. [deprecated] startIntentSenderForResult()
    ComponentActivity
    • 新しい⽅法 : ActivityResultContract を使う
    FragmentActivity
    [deprecated] startIntentSenderFromFragment()

    View Slide

  19. 新しい⽅法
    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(
    )

    )

    }

    }

    View Slide

  20. [deprecated] onAttachFragment()
    FragmentActivity
    • 新しい⽅法 : FragmentManager の addFragmentOnAttachListener() を使う

    View Slide

  21. 以前の⽅法
    class MainActivity : FragmentActivity()
    {

    override fun onAttachFragment(fragment: Fragment)
    {

    super.onAttachFragment(fragment
    )

    if (fragment is MainFragment)
    {

    // do somethin
    g

    }

    }



    }

    View Slide

  22. 新しい⽅法
    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
    )

    }


    }

    View Slide

  23. OnBackPressedDispatcher
    ComponentActivity
    • Back ボタンが押されたときの処理を登録できるようになった

    View Slide

  24. 以前の⽅法
    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

    }

    }

    View Slide

  25. 新しい⽅法
    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

    }

    }

    View Slide

  26. Fragment

    View Slide

  27. FragmentContainerView
    • 以前の⽅法
    • レイアウト XML で タグを使う
    • FrameLayout を Fragment の container として使う
    • 新しい⽅法
    • レイアウト XML で タグを
    使う
    • FragmentContainerView を Fragment の container として使う

    View Slide

  28. 以前の⽅法
    t

    android:id="@+id/main_fragment
    "

    android:name="com.sample.myapplication.MainFragment
    "

    android:layout_width="match_parent
    "

    android:layout_height="match_parent" /
    >

    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(
    )

    View Slide

  29. 新しい⽅法
    w

    android:id="@+id/main_fragment
    "

    android:name="com.sample.myapplication.MainFragment
    "

    android:layout_width="match_parent
    "

    android:layout_height="match_parent" /
    >

    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(
    )

    View Slide

  30. 新しい⽅法
    w

    android:id="@+id/main_fragment
    "

    android:name="com.sample.myapplication.MainFragment
    "

    android:layout_width="match_parent
    "

    android:layout_height="match_parent" /
    >

    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(R.id.fragment_container
    )

    val f = fragmentContainerView.getFragment()

    View Slide

  31. Fragment のコンストラクタでレイアウトXMLを指定
    • レイアウトXMLを指定できるコンストラクタが⽤意された
    • 指定したレイアウトXMLの View が onCreateView() で⽣成される

    View Slide

  32. 以前の⽅法
    新しい⽅法
    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
    )



    }

    View Slide

  33. FragmentFactory
    • 以前の⽅法
    • Fragment のインスタンスはライブラリによって⽣成させるので、引数なし
    のコンストラクタでなくてはならない
    • 新しい⽅法
    • FragmentFactory を使えば、引数ありのコンストラクタが使える

    View Slide

  34. 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
    )



    }



    }
    ৽͍͠ํ๏

    View Slide

  35. [deprecated] getFragmentManager(), requireFragmentManager()
    (androidx) Fragment
    • 新しい⽅法 : getParentFragmentManager() を使う

    View Slide

  36. [deprecated] onAttachFragment()
    (androidx) Fragment
    • 新しい⽅法 : getChildFragmentManager() で取得した FragmentManager の
    addFragmentOnAttachListener() を 使う

    View Slide

  37. 新しい⽅法
    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
    )

    }



    }

    View Slide

  38. [deprecated] onActivityCreated()
    (androidx) Fragment
    • 新しい⽅法 : onViewCreated() を使う
    • Activity の onCreate が呼ばれたときのコールバックが必要な場合は onAttach
    で LifecycleObserver を登録する

    View Slide

  39. 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 を登録する

    View Slide

  40. [deprecated] getRetainInstance(), setRetainInstance()
    (androidx) Fragment
    • 新しい⽅法 : non-retained Fragment を使い、ViewModel で状態を保持する
    ようにする

    View Slide

  41. 以前の⽅法
    class OldFragment : Fragment()
    {

    private val data = MutableLiveData>(
    )

    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

    }

    }

    }

    View Slide

  42. 新しい⽅法
    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>(
    )

    val data: LiveData
    >

    get() = _dat
    a

    init
    {

    viewModelScope.launch
    {

    _data.value = …
    }

    }

    }

    View Slide

  43. [deprecated] startActivityForResult(), onActivityResult()
    • 新しい⽅法 : ActivityResultContract を使う
    [deprecated] startIntentSenderForResult()
    (androidx) Fragment
    [deprecated] onRequestPermissionsResult()
    [deprecated] requestPermissions()

    View Slide

  44. 新しい⽅法
    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)
    )

    }


    }

    View Slide

  45. [deprecated] getTargetFragment() , setTargetFragment()
    (androidx) Fragment
    • 親⼦関係の Fragment 間でデータを受け渡す場合、以前は targetFragment を
    利⽤していたが deprecated になった
    • 新しい⽅法 : Fragment Result API をつかう

    View Slide

  46. 以前の⽅法
    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()
    )

    }

    }

    }

    View Slide

  47. 新しい⽅法
    https://developer.android.com/guide/fragments/communicate#fragment-result

    View Slide

  48. 新しい⽅法
    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 間
    で使える

    View Slide

  49. [deprecated] FragmentPagerAdapter, FragmentStatePagerAdapter
    • ViewPager2 が stable になったことで、ViewPager 向けの Fragment Adapter
    は deprecated になった
    • 新しい⽅法 : ViewPager2 をつかう

    View Slide

  50. 以前の⽅法
    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


    }

    }

    View Slide

  51. 新しい⽅法
    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


    }

    }

    View Slide

  52. 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

    View Slide

  53. penaltyDeath() Ϋϥογϡ
    penaltyListener { violation -> } ηοτͨ͠Ϧεφʔ͕ݺ͹ΕΔ
    penaltyLog() ϩάʢdebug levelʣʹग़ྗ
    ペナルティの種類

    View Slide

  54. detectFragmentReuse() FragmentManager ͔Β remove ͞Εͨ͋ͱͷ Fragment Λ࠶ར༻͍ͯ͠Δ͔
    detectFragmentTagUsage() ϨΠΞ΢τXML Ͱ λάΛ࢖͍ͬͯΔ͔

    detectRetainInstanceUsage() setRetainInstance() / getRetainInstance() ΛݺΜͰ͍Δ͔
    detectSetUserVisibleHint() setUserVisibleHint() ΛݺΜͰ͍Δ͔
    detectTargetFragmentUsage() setTargetFragment() / getTargetFragment() / getTargetRequestCode() ΛݺΜͰ͍Δ͔
    detectWrongFragmentContainer() FragmentContainerView Ҏ֎ͷ View ʹ Fragment ͕௥Ճ͞Ε͍ͯΔ͔
    検出できる違反の種類

    View Slide

  55. Lifecycle

    View Slide

  56. [deprecated] @OnLifecycleEvent
    androidx.lifecycle
    • 新しい⽅法 : DefaultLifecycleObserver か LifecycleEventObserver を使う

    View Slide

  57. 以前の⽅法
    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
    )

    }

    }
    )

    }



    }

    View Slide

  58. 新しい⽅法
    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

    View Slide

  59. 新しい⽅法
    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
    )

    }

    }

    }

    }
    )

    }



    }

    View Slide

  60. まとめ
    • startActivityForResult() や requestPermission() には ActivityResultContract
    を使う
    • Fragment は書き⽅がいろいろ変わっている
    • Fragment ⽤の StrictMode で違反を簡単に検出できる
    • deprecated になった機能は早め早めに対応しよう

    View Slide