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.

Kirill Rozov

June 27, 2020
Tweet

More Decks by Kirill Rozov

Other Decks in Programming

Transcript

  1. • 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
  2. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } CONTENT VIEW 6
  3. 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
  4. 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
  5. 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
  6. • CreateDocument • OpenDocument • OpenDocumentTree • OpenMultipleDocuments • GetContent

    • GetMultipleContent • PickContact • StartActivityForResult • StartIntentSenderForResult • TakePicture • TakePicturePreview • TakeVideo • RequestPermission • RequestMultiplePermissions PREDEFINED CONTRACTS 11
  7. 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
  8. 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
  9. 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
  10. class EndFragment : Fragment() { fun updateResult(success: Boolean) { val

    result = bundleOf(KEY_SUCCESS to success) parentFragmentManager.setFragmentResult(REQUEST_KEY, result) } } FRAGMENT RESULT API 18
  11. 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
  12. "# 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
  13. • Injection in Fragment constructor • No more Component.inject(*Fragment) •

    Less re lection/more performance • Possibility to obfuscate Fragments FRAGMENT FACTORY BONUSES 27
  14. 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
  15. val attrs = context.obtainStyledAttributes( attributes, R.styleable.CustomView, defStyleAttr, 0 ) main

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

    ) { main = attrs.getBoolean(R.styleable.CustomView_main, false) } CUSTOM VIEW ATTRIBUTES 38
  17. 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
  18. class MainFragment : Fragment() { private val viewModel: MainViewModel =

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

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

    Fragment() { private val viewModel: MainViewModel by viewModels { viewModelFactory.get() } } VIEW MODEL CREATION 47
  21. "# androidx.lifecycle:lifecycle-viewmodel-savedstate"+version> class MainViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val text:

    MutableLiveData<String> = savedStateHandle.getLiveData(STATE_TEXT) } VIEW MODEL. SAVING STATE 49
  22. 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
  23. <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
  24. 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" } }
  25. • No preview • All ids by your hands •

    No “safe args” plugins NAVIGATION GRAPH. KOTLIN DSL 54
  26. • 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