Slide 1

Slide 1 text

SECRETS OF ANDROID JETPACK KIRILL ROZOV / REPLIKA.AI

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

• 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

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

ACTIVITY

Slide 6

Slide 6 text

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } CONTENT VIEW 6

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

FRAGMENT

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

class EndFragment : Fragment() { fun updateResult(success: Boolean) { val result = bundleOf(KEY_SUCCESS to success) parentFragmentManager.setFragmentResult(REQUEST_KEY, result) } } FRAGMENT RESULT API 18

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

class FragmentFactory { open fun instantiate( classLoader: ClassLoader, className: String ): Fragment } FRAGMENT FACTORY 24

Slide 25

Slide 25 text

class InjectFragmentFactory( providers: Map, Provider

Slide 26

Slide 26 text

"# 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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

fragmentManager.commit { add(android.R.id.content, NextFragment(), FRAGMENT_TAG) } FRAGMENT TRANSCATION 28

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

ANDROIDX CORE

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

buildSpannedString { italic { append("Hello") } append(",") bold { append("World!") } } BUILD SPANNED STRING 34

Slide 34

Slide 34 text

val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) val activityManager = context.getSystemService() SYSTEM SERVICES 35

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

val attrs = context.obtainStyledAttributes( attributes, R.styleable.CustomView, defStyleAttr, 0 ) main = attrs.getBoolean(R.styleable.CustomView_main, false) attrs.recycle() CUSTOM VIEW ATTRIBUTES 37

Slide 37

Slide 37 text

context.withStyledAttributes( set = attributes, attrs = R.styleable.CustomView, defStyleAttr = defStyleAttr ) { main = attrs.getBoolean(R.styleable.CustomView_main, false) } CUSTOM VIEW ATTRIBUTES 38

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

LIFECYCLE

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

class MainFragment : Fragment() { private val viewModel: MainViewModel by viewModels { MainViewModel.Factory() } } VIEW MODEL CREATION 46

Slide 46

Slide 46 text

class MainFragment @Inject constructor( private val viewModelFactory: Provider ) : Fragment() { private val viewModel: MainViewModel by viewModels { viewModelFactory.get() } } VIEW MODEL CREATION 47

Slide 47

Slide 47 text

VIEWMODEL. SAVING STATE 48

Slide 48

Slide 48 text

"# androidx.lifecycle:lifecycle-viewmodel-savedstate"+version> class MainViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val text: MutableLiveData = savedStateHandle.getLiveData(STATE_TEXT) } VIEW MODEL. SAVING STATE 49

Slide 49

Slide 49 text

class MainViewModel(private val githubService: GithubService) : ViewModel() { val users: LiveData

Slide 50

Slide 50 text

NAVIGATION

Slide 51

Slide 51 text

"-navigation> NAVIGATION GRAPH. XML 52

Slide 52

Slide 52 text

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" } }

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

• 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

Slide 55

Slide 55 text

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