$30 off During Our Annual Pro Sale. View Details »

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.

Kirill Rozov

June 27, 2020
Tweet

More Decks by Kirill Rozov

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  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

    View Slide

  4. View Slide

  5. ACTIVITY

    View Slide

  6. class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)

    }

    }

    CONTENT VIEW
    6

    View Slide

  7. class MainActivity : AppCompatActivity(R.layout.activity_main)
    CONTENT VIEW
    7

    View Slide

  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

    View Slide

  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

    View Slide

  10. class TakePicture : ActivityResultContract() {

    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? {

    return null

    }

    override fun parseResult(resultCode: Int, intent: Intent?): Boolean {

    return resultCode "$ Activity.RESULT_OK

    }

    }
    ACTIVITY RESULT API
    10

    View Slide

  11. • CreateDocument
    • OpenDocument
    • OpenDocumentTree
    • OpenMultipleDocuments
    • GetContent
    • GetMultipleContent
    • PickContact
    • StartActivityForResult
    • StartIntentSenderForResult
    • TakePicture
    • TakePicturePreview
    • TakeVideo
    • RequestPermission
    • RequestMultiplePermissions
    PREDEFINED CONTRACTS
    11

    View Slide

  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

    View Slide

  13. FRAGMENT

    View Slide

  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

    View Slide

  15. class MainFragment : Fragment(R.layout.fragment_main)
    FRAGMENT’S VIEW
    15

    View Slide

  16. • Callback via parent Fragment/Activity
    • Target Fragment
    • Shared View Model
    SHARE RESULT
    BETWEEN FRAGMENTS
    16

    View Slide

  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

    View Slide

  18. class EndFragment : Fragment() {

    fun updateResult(success: Boolean) {

    val result = bundleOf(KEY_SUCCESS to success)

    parentFragmentManager.setFragmentResult(REQUEST_KEY, result)

    }

    }
    FRAGMENT RESULT API
    18

    View Slide

  19. class MainFragment : Fragment() {

    @Inject

    lateinit var deps: Deps

    }
    DI IN FRAGMENT
    19

    View Slide

  20. class MainFragment : Fragment() {

    @Inject

    private lateinit var deps: Deps

    }
    DI IN FRAGMENT
    20

    View Slide

  21. class MainFragment : Fragment() {

    @Inject

    private var deps: Deps

    }
    DI IN FRAGMENT
    21

    View Slide

  22. class MainFragment : Fragment() {

    @Inject

    private val deps: Deps

    }
    DI IN FRAGMENT
    22

    View Slide

  23. class MainFragment @Inject constructor(

    private val deps: Deps

    ) : Fragment()
    DI IN FRAGMENT
    23

    View Slide

  24. class FragmentFactory {

    open fun instantiate(

    classLoader: ClassLoader,

    className: String

    ): Fragment

    }
    FRAGMENT FACTORY
    24

    View Slide

  25. class InjectFragmentFactory(

    providers: Map, Provider
    ) : 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

    View Slide

  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

    View Slide

  27. • Injection in Fragment constructor
    • No more Component.inject(*Fragment)
    • Less re lection/more performance
    • Possibility to obfuscate Fragments
    FRAGMENT FACTORY
    BONUSES
    27

    View Slide

  28. fragmentManager.commit {

    add(android.R.id.content, NextFragment(), FRAGMENT_TAG)

    }
    FRAGMENT TRANSCATION
    28

    View Slide

  29. fragmentManager.commit {

    add(

    android.R.id.content,

    fragmentClass = NextFragment")class.java,

    args = null,

    tag = FRAGMENT_TAG

    )

    }
    FRAGMENT TRANSCATION
    29

    View Slide

  30. fragmentManager.commit {

    add(

    android.R.id.content,

    args = null,

    tag = FRAGMENT_TAG

    )

    }

    FRAGMENT TRANSCATION
    30

    View Slide

  31. ANDROIDX CORE

    View Slide

  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

    View Slide

  33. buildSpannedString {

    italic { append("Hello") }

    append(",")

    bold { append("World!") }

    }
    BUILD SPANNED STRING
    34

    View Slide

  34. val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE)

    val activityManager = context.getSystemService()
    SYSTEM SERVICES
    35

    View Slide

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

    val activityManager: ActivityManager? = context.getSystemService()
    SYSTEM SERVICES
    36

    View Slide

  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

    View Slide

  37. context.withStyledAttributes(

    set = attributes,

    attrs = R.styleable.CustomView,

    defStyleAttr = defStyleAttr

    ) {

    main = attrs.getBoolean(R.styleable.CustomView_main, false)

    }

    CUSTOM VIEW ATTRIBUTES
    38

    View Slide

  38. fun makeArgs(): Bundle {

    val args = Bundle()

    args.putString(KEY1, "value")

    args.putInt(KEY2, 1)

    return args

    }
    BUNDLE
    39

    View Slide

  39. fun makeArgs(): Bundle {

    return Bundle().apply {

    putString(KEY1, "value")

    putInt(KEY2, 1)

    }

    }
    BUNDLE
    40

    View Slide

  40. fun makeArgs(): Bundle {

    return Bundle(2).apply {

    putString(KEY1, "value")

    putInt(KEY2, 1)

    }

    }
    BUNDLE
    41

    View Slide

  41. fun makeArgs(): Bundle {

    return bundleOf(KEY1 to "value", KEY2 to 1)

    }
    BUNDLE
    42

    View Slide

  42. fun bundleOf(vararg pairs: Pair) = 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

    View Slide

  43. LIFECYCLE

    View Slide

  44. class MainFragment : Fragment() {

    private val viewModel: MainViewModel =

    ViewModelProvider(

    this,

    MainViewModel.Factory()

    ).get(MainViewModel")class.java)

    }
    VIEW MODEL CREATION
    45

    View Slide

  45. class MainFragment : Fragment() {

    private val viewModel: MainViewModel by viewModels {
    MainViewModel.Factory()
    }

    }
    VIEW MODEL CREATION
    46

    View Slide

  46. class MainFragment @Inject constructor(

    private val viewModelFactory: Provider

    ) : Fragment() {

    private val viewModel: MainViewModel by viewModels {
    viewModelFactory.get()
    }

    }
    VIEW MODEL CREATION
    47

    View Slide

  47. VIEWMODEL. SAVING STATE
    48

    View Slide

  48. "# androidx.lifecycle:lifecycle-viewmodel-savedstate"+version>

    class MainViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    val text: MutableLiveData =

    savedStateHandle.getLiveData(STATE_TEXT)

    }
    VIEW MODEL. SAVING STATE
    49

    View Slide

  49. class MainViewModel(private val githubService: GithubService) : ViewModel() {

    val users: LiveData
    while (true) {

    var delayDuration = 5_000L "# Ms

    try {

    emit(githubService.fetchUsers())

    break

    } catch (e: Exception) {

    delay(delayDuration)

    delayDuration *= 2

    }

    }

    }

    }
    LIVEDATA BUILDER + COROUTINE
    50

    View Slide

  50. NAVIGATION

    View Slide




  51. android:name=“HomeViewPagerFragment >


    app:destination=“@id/plant_detail_fragment" ",

    "-fragment>


    android:name="PlantDetailFragment"

    android:label=“@string/plant_detail_title" >


    "-fragment>

    "-navigation>
    NAVIGATION GRAPH. XML
    52

    View Slide

  52. navHostFragment.navController.apply {

    graph = createGraph(AppNavGraph.id, AppNavGraph.Dest.home) {

    fragment(AppNavGraph.Dest.home) {

    label = getString(R.string.home_title)

    action(AppNavGraph.Action.toPlantDetail) {

    destinationId = AppNavGraph.Dest.plantDetail

    }

    }

    fragment(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"

    }

    }

    View Slide

  53. • No preview
    • All ids by your hands
    • No “safe args” plugins
    NAVIGATION GRAPH.
    KOTLIN DSL
    54

    View Slide

  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

    View Slide

  55. THANKS YOU!
    QUESTIONS TIME
    KIRILL ROZOV / REPLIKA.AI
    @kirill_rozov
    @krlrozov
    youtube.com/c/androidbroadcast

    View Slide