Slide 1

Slide 1 text

DevFest Tokyo 2021 Yuki Anzai(@yanzm)  GDE for Android  その書き⽅古いかも。

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Android Framework

Slide 4

Slide 4 text

Activity

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

android.preference

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

AndroidX

Slide 10

Slide 10 text

ComponentActivity FragmentActivity

Slide 11

Slide 11 text

継承関係 androidx • ComponentActivity • FragmentActivity • AppCompatActivity

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

以前の⽅法 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 } } } … }

Slide 17

Slide 17 text

新しい⽅法 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 ) } … }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

以前の⽅法 class MainActivity : FragmentActivity() { override fun onAttachFragment(fragment: Fragment) { super.onAttachFragment(fragment ) if (fragment is MainFragment) { // do somethin g } } … }

Slide 22

Slide 22 text

新しい⽅法 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 ) } … }

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Fragment

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

以前の⽅法 supportFragmentManager.beginTransaction( ) .replace(R.id.fragment_container, MainFragment() ) .commit( )

Slide 29

Slide 29 text

新しい⽅法 supportFragmentManager.beginTransaction( ) .replace(R.id.fragment_container, MainFragment() ) .commit( )

Slide 30

Slide 30 text

新しい⽅法 supportFragmentManager.beginTransaction( ) .replace(R.id.fragment_container, MainFragment() ) .commit( ) • getFragment() で⼀番最後に追加された Fragment を取得できる val fragmentContainerView = findViewById(R.id.fragment_container ) val f = fragmentContainerView.getFragment()

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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 ) … } … } ৽͍͠ํ๏

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

新しい⽅法 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 ) } … }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

新しい⽅法 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 = … } } }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

新しい⽅法 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) ) } … }

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

以前の⽅法 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 … } }

Slide 51

Slide 51 text

新しい⽅法 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 … } }

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

detectFragmentReuse() FragmentManager ͔Β remove ͞Εͨ͋ͱͷ Fragment Λ࠶ར༻͍ͯ͠Δ͔ detectFragmentTagUsage() ϨΠΞ΢τXML Ͱ λάΛ࢖͍ͬͯΔ͔ detectRetainInstanceUsage() setRetainInstance() / getRetainInstance() ΛݺΜͰ͍Δ͔ detectSetUserVisibleHint() setUserVisibleHint() ΛݺΜͰ͍Δ͔ detectTargetFragmentUsage() setTargetFragment() / getTargetFragment() / getTargetRequestCode() ΛݺΜͰ͍Δ͔ detectWrongFragmentContainer() FragmentContainerView Ҏ֎ͷ View ʹ Fragment ͕௥Ճ͞Ε͍ͯΔ͔ 検出できる違反の種類

Slide 55

Slide 55 text

Lifecycle

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

以前の⽅法 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 ) } } ) } … }

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

新しい⽅法 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 ) } } } } ) } … }

Slide 60

Slide 60 text

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