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

Secrets of Android Jetpack

Secrets of Android Jetpack

In recent years, Android Jetpack has been developing at an incredible pace and keeping track of all the new products is not easy. The report will provide an overview of the latest API and the future of their AndroidX, which are changing the approach to developing Android apps.

2aec47eb9a940c619f05972f0db5aa00?s=128

Kirill Rozov

June 27, 2020
Tweet

Transcript

  1. SECRETS OF ANDROID JETPACK KIRILL ROZOV / REPLIKA.AI

  2. • Activity • Fragment • Core • ViewModel • LiveData

    • Navigation AGENDA 2
  3. • Develop Android apps for 8+ years • Kotlin Lover

    • Mobile Lead at Replika.ai • Author of “Android Broadcast” Project • Cohost of Android Dev Virtual Meetups • Cohost of Mobile People Talks podcast WHO AM I? KIRILL ROZOV @kirill_rozov @krlrozov
  4. None
  5. ACTIVITY

  6. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } CONTENT VIEW 6
  7. class MainActivity : AppCompatActivity(R.layout.activity_main) CONTENT VIEW 7

  8. class MainActivity : AppCompatActivity() { var pictureUri: Uri? = null

    fun onPictureTaken(uri: Uri?, isImageSaved: Boolean) { "# Process the image } fun takePicture() { val pictureUri: Uri = Uri.fromFile(File.createTempFile("androidx-secrets", System.currentTimeMillis().toString())) this.pictureUri = pictureUri val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, pictureUri) startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode "$ REQUEST_CODE_TAKE_PHOTO) { onPictureTaken(pictureUri, resultCode "$ Activity.RESULT_OK) } else { super.onActivityResult(requestCode, resultCode, data) } } companion object { const val REQUEST_CODE_TAKE_PHOTO = 1 } } ACTIVITY RESULT 8
  9. class MainActivity : AppCompatActivity() { var pictureUri: Uri? = null

    val takePhoto = registerForActivityResult(TakePicture()) { isImageSaved "# onPictureTaken(pictureUri, isImageSaved) this.pictureUri = null } fun onPictureTaken(uri: Uri?, isImageSaved: Boolean) { "# Process the image } fun takePicture() { val pictureUri = Uri.fromFile(File.createTempFile("androidx-secrets", System.currentTimeMillis().toString())) this.pictureUri = pictureUri takePhoto.launch(pictureUri) } ACTIVITY RESULT API 9
  10. class TakePicture : ActivityResultContract<Uri, Boolean>() { override fun createIntent(context: Context,

    input: Uri): Intent { return Intent(MediaStore.ACTION_IMAGE_CAPTURE) .putExtra(MediaStore.EXTRA_OUTPUT, input) } override fun getSynchronousResult( context: Context, input: Uri ): SynchronousResult<Boolean>? { return null } override fun parseResult(resultCode: Int, intent: Intent?): Boolean { return resultCode "$ Activity.RESULT_OK } } ACTIVITY RESULT API 10
  11. • CreateDocument • OpenDocument • OpenDocumentTree • OpenMultipleDocuments • GetContent

    • GetMultipleContent • PickContact • StartActivityForResult • StartIntentSenderForResult • TakePicture • TakePicturePreview • TakeVideo • RequestPermission • RequestMultiplePermissions PREDEFINED CONTRACTS 11
  12. class MainActivity : AppCompatActivity() { val requestPermissionForTrackUserResult = registerForActivityResult(RequestPermission()) {

    permissionGranted "# if (permissionGranted) { trackUser() } } @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) fun trackUser() { "# Start track user } fun requestPermissionForTrackUser() { requestPermissionForTrackUserResult.launch(Manifest.permission.ACCESS_FINE_LOCATION) } } ACTIVITY RESULT API FOR PERMISSIONS 12
  13. FRAGMENT

  14. class MainFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater,

    container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_main, container, false) } } FRAGMENT’S VIEW 14
  15. class MainFragment : Fragment(R.layout.fragment_main) FRAGMENT’S VIEW 15

  16. • Callback via parent Fragment/Activity • Target Fragment • Shared

    View Model SHARE RESULT BETWEEN FRAGMENTS 16
  17. class FromFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) childFragmentManager.setFragmentResultListener( EndFragment.REQUEST_KEY, this ) { requestKey: String, result: Bundle "# val isSuccess = result.getBoolean(EndFragment.KEY_SUCCESS) "# Do something with the result"% } } fun startFragment() { childFragmentManager.commit { add(android.R.id.content, EndFragment(), FRAGMENT_END) } } } FRAGMENT RESULT API 17
  18. class EndFragment : Fragment() { fun updateResult(success: Boolean) { val

    result = bundleOf(KEY_SUCCESS to success) parentFragmentManager.setFragmentResult(REQUEST_KEY, result) } } FRAGMENT RESULT API 18
  19. class MainFragment : Fragment() { @Inject lateinit var deps: Deps

    } DI IN FRAGMENT 19
  20. class MainFragment : Fragment() { @Inject private lateinit var deps:

    Deps } DI IN FRAGMENT 20
  21. class MainFragment : Fragment() { @Inject private var deps: Deps

    } DI IN FRAGMENT 21
  22. class MainFragment : Fragment() { @Inject private val deps: Deps

    } DI IN FRAGMENT 22
  23. class MainFragment @Inject constructor( private val deps: Deps ) :

    Fragment() DI IN FRAGMENT 23
  24. class FragmentFactory { open fun instantiate( classLoader: ClassLoader, className: String

    ): Fragment } FRAGMENT FACTORY 24
  25. class InjectFragmentFactory( providers: Map<Class<out Fragment>, Provider<Fragment"& ) : FragmentFactory() {

    private val providers = providers.mapKeys { (fragmentClass, _) "# fragmentClass.name } override fun instantiate( classLoader: ClassLoader, className: String ): Fragment { return providers[className]"'get() "( super.instantiate(classLoader, className) } } FRAGMENT FACTORY 25
  26. "# Set the fragmentFactory as default for every FragmentActivity in

    the app class SetFragmentFactory( private val fragmentFactory: FragmentFactory ) : Application.ActivityLifecycleCallbacks { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { if (activity is FragmentActivity) { activity.supportFragmentManager.fragmentFactory = this.fragmentFactory } } } FRAGMENT FACTORY 26
  27. • Injection in Fragment constructor • No more Component.inject(*Fragment) •

    Less re lection/more performance • Possibility to obfuscate Fragments FRAGMENT FACTORY BONUSES 27
  28. fragmentManager.commit { add(android.R.id.content, NextFragment(), FRAGMENT_TAG) } FRAGMENT TRANSCATION 28

  29. fragmentManager.commit { add( android.R.id.content, fragmentClass = NextFragment")class.java, args = null,

    tag = FRAGMENT_TAG ) } FRAGMENT TRANSCATION 29
  30. fragmentManager.commit { add<NextFragment>( android.R.id.content, args = null, tag = FRAGMENT_TAG

    ) } FRAGMENT TRANSCATION 30
  31. ANDROIDX CORE

  32. val builder = SpannableStringBuilder() builder.append("Hello") builder.setSpan( StyleSpan(Typeface.ITALIC), 0, builder.length, SPAN_INCLUSIVE_EXCLUSIVE

    ) builder.append(",") val start = builder.length builder.append("World!") builder.setSpan( StyleSpan(Typeface.BOLD), start, builder.length, SPAN_INCLUSIVE_EXCLUSIVE ) BUILD SPANNED STRING 33
  33. buildSpannedString { italic { append("Hello") } append(",") bold { append("World!")

    } } BUILD SPANNED STRING 34
  34. val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) val activityManager = context.getSystemService() SYSTEM SERVICES

    35
  35. val activityManager: Any? = context.getSystemService(Context.ACTIVITY_SERVICE) val activityManager: ActivityManager? = context.getSystemService()

    SYSTEM SERVICES 36
  36. val attrs = context.obtainStyledAttributes( attributes, R.styleable.CustomView, defStyleAttr, 0 ) main

    = attrs.getBoolean(R.styleable.CustomView_main, false) attrs.recycle() CUSTOM VIEW ATTRIBUTES 37
  37. context.withStyledAttributes( set = attributes, attrs = R.styleable.CustomView, defStyleAttr = defStyleAttr

    ) { main = attrs.getBoolean(R.styleable.CustomView_main, false) } CUSTOM VIEW ATTRIBUTES 38
  38. fun makeArgs(): Bundle { val args = Bundle() args.putString(KEY1, "value")

    args.putInt(KEY2, 1) return args } BUNDLE 39
  39. fun makeArgs(): Bundle { return Bundle().apply { putString(KEY1, "value") putInt(KEY2,

    1) } } BUNDLE 40
  40. fun makeArgs(): Bundle { return Bundle(2).apply { putString(KEY1, "value") putInt(KEY2,

    1) } } BUNDLE 41
  41. fun makeArgs(): Bundle { return bundleOf(KEY1 to "value", KEY2 to

    1) } BUNDLE 42
  42. fun bundleOf(vararg pairs: Pair<String, Any?>) = Bundle(pairs.size).apply { for ((key,

    value) in pairs) { when (value) { null "* putString(key, null) is Boolean "* putBoolean(key, value) is Byte "* putByte(key, value) is Char "* putChar(key, value) is Double "* putDouble(key, value) is Float "* putFloat(key, value) is Int "* putInt(key, value) is Long "* putLong(key, value) is Short "* putShort(key, value) … } } } ⚠ UNDER THE HOOD 43
  43. LIFECYCLE

  44. class MainFragment : Fragment() { private val viewModel: MainViewModel =

    ViewModelProvider( this, MainViewModel.Factory() ).get(MainViewModel")class.java) } VIEW MODEL CREATION 45
  45. class MainFragment : Fragment() { private val viewModel: MainViewModel by

    viewModels { MainViewModel.Factory() } } VIEW MODEL CREATION 46
  46. class MainFragment @Inject constructor( private val viewModelFactory: Provider<MainViewModel.Factory> ) :

    Fragment() { private val viewModel: MainViewModel by viewModels { viewModelFactory.get() } } VIEW MODEL CREATION 47
  47. VIEWMODEL. SAVING STATE 48

  48. "# androidx.lifecycle:lifecycle-viewmodel-savedstate"+version> class MainViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val text:

    MutableLiveData<String> = savedStateHandle.getLiveData(STATE_TEXT) } VIEW MODEL. SAVING STATE 49
  49. class MainViewModel(private val githubService: GithubService) : ViewModel() { val users:

    LiveData<List<User"& = liveData { while (true) { var delayDuration = 5_000L "# Ms try { emit(githubService.fetchUsers()) break } catch (e: Exception) { delay(delayDuration) delayDuration *= 2 } } } } LIVEDATA BUILDER + COROUTINE 50
  50. NAVIGATION

  51. <navigation app:startDestination=“@id/home_fragment" > <fragment android:id="@+id/home_fragment" android:name=“HomeViewPagerFragment > <action android:id="@+id/action_home_to_plant_detail" app:destination=“@id/plant_detail_fragment"

    ", "-fragment> <fragment android:id="@+id/plant_detail_fragment" android:name="PlantDetailFragment" android:label=“@string/plant_detail_title" > <argument android:name=“plantId" app:argType=“string" ", "-fragment> "-navigation> NAVIGATION GRAPH. XML 52
  52. navHostFragment.navController.apply { graph = createGraph(AppNavGraph.id, AppNavGraph.Dest.home) { fragment<HomeViewPagerFragment>(AppNavGraph.Dest.home) { label

    = getString(R.string.home_title) action(AppNavGraph.Action.toPlantDetail) { destinationId = AppNavGraph.Dest.plantDetail } } fragment<PlantDetailFragment>(AppNavGraph.Dest.plantDetail) { label = getString(R.string.plant_detail_title) argument(AppNavGraph.Args.plantId) { type = NavType.StringType } } } } NAVIGATION GRAPH. KOTLIN DSL 53 object AppNavGraph { const val id = 1 object Dest { const val home = 2 const val plantDetail = 3 } object Action { const val toPlantDetail = 4 } object Args { const val plantId = "plantId" } }
  53. • No preview • All ids by your hands •

    No “safe args” plugins NAVIGATION GRAPH. KOTLIN DSL 54
  54. • Hilt for Dagger 2 (Alpha) • Paging 3.0 (Alpha)

    • Benchmark • Startup (Alpha) • CameraX (Beta) • AsyncLayoutIn later • Constraint Layout 2.0 (Beta) • MotionLayout (Beta) • Vector Drawable • SavedState • Exif Interface • RecyclerView • and many more… MORE JETPACK LIBRARIES 55
  55. THANKS YOU! QUESTIONS TIME KIRILL ROZOV / REPLIKA.AI @kirill_rozov @krlrozov

    youtube.com/c/androidbroadcast