DevFest Tokyo 2021Yuki Anzai(@yanzm) GDE for Android その書き⽅古いかも。
View Slide
最近のリリース• 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
Android Framework
Activity
[deprecated] onStateNotSaved()Activity• API Level 28(Android P)から onSaveInstanceState() は onStop() の後に呼ばれるようになった• ↑に伴い、このメソッドは不正確になったので廃⽌
android.preference
[deprecated] android.preference• 新しい⽅法• AndroidX の Preference Library (androidx.preference) を使う
[deprecated] android.preference• 新しい⽅法• AndroidX の Preference Library (androidx.preference) を使うSharedPreferences → Android フレームワーク(key-value pair を保存する仕組み)設定画⾯を簡単に作る機能 → AndroidX(SharedPreferences を (ライブラリ)設定値保存に使⽤)
AndroidX
ComponentActivityFragmentActivity
継承関係androidx• ComponentActivity• FragmentActivity• AppCompatActivity
[deprecated] startActivityForResult(), onActivityResult()ComponentActivity• 新しい⽅法 : ActivityResultContract を使う
以前の⽅法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){// ...}}}}
新しい⽅法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))}}
[deprecated] onRequestPermissionsResult()ComponentActivity• 新しい⽅法 : ActivityResultContract を使う
以前の⽅法class OldMainActivity : ComponentActivity(){private fun requestCameraPermission(){requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CODE)}override fun onRequestPermissionsResult(requestCode: Int,permissions: Array,grantResults: IntArray){super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode == REQUEST_CODE){if (grantResults[0] == PackageManager.PERMISSION_GRANTED){doSomethingWithCamera()} else{// show message}}}…}
新しい⽅法class MainActivity : ComponentActivity(){private val requestCameraPermissionLauncher=registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->if (granted){doSomethingWithCamera()} else{// show message}}private fun requestCameraPermission(){requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)}…}
[deprecated] startIntentSenderForResult()ComponentActivity• 新しい⽅法 : ActivityResultContract を使うFragmentActivity[deprecated] startIntentSenderFromFragment()
新しい⽅法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())}}
[deprecated] onAttachFragment()FragmentActivity• 新しい⽅法 : FragmentManager の addFragmentOnAttachListener() を使う
以前の⽅法class MainActivity : FragmentActivity(){override fun onAttachFragment(fragment: Fragment){super.onAttachFragment(fragment)if (fragment is MainFragment){// do something}}…}
新しい⽅法class MainActivity : FragmentActivity(){private val listener = FragmentOnAttachListener { fragmentManager, fragment ->if (fragment is MainFragment){// do something}}override fun onCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)supportFragmentManager.addFragmentOnAttachListener(listener)}…}
OnBackPressedDispatcherComponentActivity• Back ボタンが押されたときの処理を登録できるようになった
以前の⽅法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 dialog}}
新しい⽅法class EditFragment : Fragment(){override fun onViewCreated(view: View, savedInstanceState: Bundle?){super.onViewCreated(view, savedInstanceState)// activity-ktxval callback = requireActivity().onBackPressedDispatcher.addCallback(owner = viewLifecycleOwner,enabled = true){// show discard confirm dialog}// callback ΛҰ࣌తʹແޮʹ͍ͨ͠ͱ͖callback.isEnabled = false}}
Fragment
FragmentContainerView• 以前の⽅法• レイアウト XML で タグを使う• FrameLayout を Fragment の container として使う• 新しい⽅法• レイアウト XML で タグを使う• FragmentContainerView を Fragment の container として使う
以前の⽅法tandroid:id="@+id/main_fragment"android:name="com.sample.myapplication.MainFragment"android:layout_width="match_parent"android:layout_height="match_parent" />tandroid:id="@+id/fragment_container"android:layout_width="match_parent"android:layout_height="match_parent" />supportFragmentManager.beginTransaction().replace(R.id.fragment_container, MainFragment()).commit()
新しい⽅法wandroid:id="@+id/main_fragment"android:name="com.sample.myapplication.MainFragment"android:layout_width="match_parent"android:layout_height="match_parent" />wandroid:id="@+id/fragment_container"android:layout_width="match_parent"android:layout_height="match_parent" />supportFragmentManager.beginTransaction().replace(R.id.fragment_container, MainFragment()).commit()
新しい⽅法wandroid:id="@+id/main_fragment"android:name="com.sample.myapplication.MainFragment"android:layout_width="match_parent"android:layout_height="match_parent" />wandroid: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()
Fragment のコンストラクタでレイアウトXMLを指定• レイアウトXMLを指定できるコンストラクタが⽤意された• 指定したレイアウトXMLの View が onCreateView() で⽣成される
以前の⽅法新しい⽅法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)…}
FragmentFactory• 以前の⽅法• Fragment のインスタンスはライブラリによって⽣成させるので、引数なしのコンストラクタでなくてはならない• 新しい⽅法• FragmentFactory を使えば、引数ありのコンストラクタが使える
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)…}…}৽͍͠ํ๏
[deprecated] getFragmentManager(), requireFragmentManager()(androidx) Fragment• 新しい⽅法 : getParentFragmentManager() を使う
[deprecated] onAttachFragment()(androidx) Fragment• 新しい⽅法 : getChildFragmentManager() で取得した FragmentManager のaddFragmentOnAttachListener() を 使う
新しい⽅法class MainFragment : Fragment(){private val listener = FragmentOnAttachListener { fragmentManager, fragment ->if (fragment is SettingFragment){// do something}}override fun onCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)childFragmentManager.addFragmentOnAttachListener(listener)}…}
[deprecated] onActivityCreated()(androidx) Fragment• 新しい⽅法 : onViewCreated() を使う• Activity の onCreate が呼ばれたときのコールバックが必要な場合は onAttachで LifecycleObserver を登録する
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 を登録する
[deprecated] getRetainInstance(), setRetainInstance()(androidx) Fragment• 新しい⽅法 : non-retained Fragment を使い、ViewModel で状態を保持するようにする
以前の⽅法class OldFragment : Fragment(){private val data = MutableLiveData>()override fun onCreate(savedInstanceState: Bundle?){retainInstance = truesuper.onCreate(savedInstanceState)lifecycleScope.launch{data.value = …}}override fun onViewCreated(view: View, savedInstanceState: Bundle?){super.onViewCreated(view, savedInstanceState)data.observe(viewLifecycleOwner){// show data}}}
新しい⽅法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 data}}}class MainViewModel : ViewModel(){private val _data = MutableLiveData>()val data: LiveData>get() = _datainit{viewModelScope.launch{_data.value = …}}}
[deprecated] startActivityForResult(), onActivityResult()• 新しい⽅法 : ActivityResultContract を使う[deprecated] startIntentSenderForResult()(androidx) Fragment[deprecated] onRequestPermissionsResult()[deprecated] requestPermissions()
新しい⽅法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))}…}
[deprecated] getTargetFragment() , setTargetFragment()(androidx) Fragment• 親⼦関係の Fragment 間でデータを受け渡す場合、以前は targetFragment を利⽤していたが deprecated になった• 新しい⽅法 : Fragment Result API をつかう
以前の⽅法class ParentFragment : Fragment(){private fun showMyDialogFragment(){val f = MyDialogFragment()f.setTargetFragment(this, 10)f.show(childFragmentManager, "MyDialogFragment")}fun onNewValue(value: Int){// show value}}class MyDialogFragment : DialogFragment(){…override fun onViewCreated(view: View, savedInstanceState: Bundle?){super.onViewCreated(view, savedInstanceState)button.setOnClickListener{(targetFragment as? ParentFragment)?.onNewValue(Random.nextInt())}}}
新しい⽅法https://developer.android.com/guide/fragments/communicate#fragment-result
新しい⽅法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 value}}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 間で使える
[deprecated] FragmentPagerAdapter, FragmentStatePagerAdapter• ViewPager2 が stable になったことで、ViewPager 向けの Fragment Adapterは deprecated になった• 新しい⽅法 : ViewPager2 をつかう
以前の⽅法class OldViewPagerActivity : FragmentActivity(){override fun onCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)val viewPager = ViewPager(this).apply{id = R.id.view_pager}setContentView(viewPager)viewPager.adapter = MyPagerAdapter(supportFragmentManager)}}class MyPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager){override fun getCount(): Int{return 10}override fun getItem(position: Int): Fragment{return…}}
新しい⽅法class NewViewPagerActivity : FragmentActivity(){override fun onCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)val viewPager = ViewPager2(this).apply{id = R.id.view_pager}setContentView(viewPager)viewPager.adapter = MyPagerAdapter(this)}}class MyPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity){override fun getItemCount(): Int{return 10}override fun createFragment(position: Int): Fragment{return…}}
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
penaltyDeath() ΫϥογϡpenaltyListener { violation -> } ηοτͨ͠Ϧεφʔ͕ݺΕΔpenaltyLog() ϩάʢdebug levelʣʹग़ྗペナルティの種類
detectFragmentReuse() FragmentManager ͔Β remove ͞Εͨ͋ͱͷ Fragment Λ࠶ར༻͍ͯ͠Δ͔detectFragmentTagUsage() ϨΠΞτXML Ͱ λάΛ͍ͬͯΔ͔detectRetainInstanceUsage() setRetainInstance() / getRetainInstance() ΛݺΜͰ͍Δ͔detectSetUserVisibleHint() setUserVisibleHint() ΛݺΜͰ͍Δ͔detectTargetFragmentUsage() setTargetFragment() / getTargetFragment() / getTargetRequestCode() ΛݺΜͰ͍Δ͔detectWrongFragmentContainer() FragmentContainerView Ҏ֎ͷ View ʹ Fragment ͕Ճ͞Ε͍ͯΔ͔検出できる違反の種類
Lifecycle
[deprecated] @OnLifecycleEventandroidx.lifecycle• 新しい⽅法 : DefaultLifecycleObserver か LifecycleEventObserver を使う
以前の⽅法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 something}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)fun onDestroy(owner: LifecycleOwner){owner.lifecycle.removeObserver(this)}})}…}
新しい⽅法DefaultLifecycleObserverclass MainActivity : FragmentActivity(){override fun onCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)lifecycle.addObserver(object : DefaultLifecycleObserver{override fun onResume(owner: LifecycleOwner){// do something}override fun onDestroy(owner: LifecycleOwner){owner.lifecycle.removeObserver(this)}})}…}Lifecycle.Event.ON_RESUMELifecycle.Event.ON_DESTROY
新しい⽅法LifecycleEventObserverclass 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 something}Lifecycle.Event.ON_DESTROY ->{owner.lifecycle.removeObserver(this)}}}})}…}
まとめ• startActivityForResult() や requestPermission() には ActivityResultContractを使う• Fragment は書き⽅がいろいろ変わっている• Fragment ⽤の StrictMode で違反を簡単に検出できる• deprecated になった機能は早め早めに対応しよう