Slide 1

Slide 1 text

@_saulmm for modern Android Development Hidden pearls and wats

Slide 2

Slide 2 text

Vigo, Galicia Saúl Molinero Saúl Molinero @_saulmm

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

@_saulmm Wat https://www.destroyallsoftware.com/talks/wat

Slide 5

Slide 5 text

@_saulmm // TODO

Slide 6

Slide 6 text

@_saulmm // TODO

Slide 7

Slide 7 text

@_saulmm android.R.string

Slide 8

Slide 8 text

@_saulmm android.R.string

Slide 9

Slide 9 text

@_saulmm android.R.string

Slide 10

Slide 10 text

@_saulmm Entities Repunantiño %1$d selected items Not happy with Repunantiño? support: 
 [email protected]

Slide 11

Slide 11 text

@_saulmm Entities ]> &app_name; &support_mail; &app_name;

Slide 12

Slide 12 text

@_saulmm archivesBaseName defaultConfig { // ... setProperty( "archivesBaseName", "popsy-$versionName@${rootProject.checkedCommit()}" ) }

Slide 13

Slide 13 text

@_saulmm archivesBaseName defaultConfig { // ... setProperty( "archivesBaseName", "popsy-$versionName@${rootProject.checkedCommit()}" ) }

Slide 14

Slide 14 text

@_saulmm Bill of Materials (BOM) / / Firebase 
 
 implementation platform(“com.google.firebase:firebase-bom:26.1.0") 
 implementation "com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-perf" implementation "com.google.firebase:firebase-messaging" implementation "com.google.firebase:firebase-config-ktx" implementation "com.google.firebase:firebase-appindexing" implementation 'com.google.firebase:firebase-inappmessaging-display' implementation 'com.google.firebase:firebase-storage' implementation 'com.google.firebase:firebase-crashlytics-ktx' https://firebase.google.com/docs/android/learn-more#compare-bom-versions

Slide 15

Slide 15 text

@_saulmm https://firebase.google.com/docs/android/learn-more#compare-bom-versions Bill of Materials (BOM)

Slide 16

Slide 16 text

@_saulmm / / Firebase 
 
 implementation platform(“com.google.firebase:firebase-bom:26.1.0") 
 implementation "com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-perf" implementation "com.google.firebase:firebase-messaging" implementation "com.google.firebase:firebase-config-ktx" implementation "com.google.firebase:firebase-appindexing" implementation 'com.google.firebase:firebase-inappmessaging-display' implementation 'com.google.firebase:firebase-storage' implementation 'com.google.firebase:firebase-crashlytics-ktx' https://firebase.google.com/docs/android/learn-more#compare-bom-versions Bill of Materials (BOM)

Slide 17

Slide 17 text

@_saulmm / / Firebase implementation “com.google.firebase:firebase-analytics:17.5.0” implementation “com.google.firebase:firebase-perf:19.0.8” implementation “com.google.firebase:firebase-messaging:20.2.4” implementation “com.google.firebase:firebase-config-ktx:19.2.0” implementation “com.google.firebase:firebase-appindexing:17.0.0” implementation ‘com.google.firebase:firebase-inappmessaging-display:19.1.0’ implementation ‘com.google.firebase:firebase-storage:19.1.1’ implementation ‘com.google.firebase:firebase-crashlytics-ktx:17.2.2’ https://firebase.google.com/docs/android/learn-more#compare-bom-versions Bill of Materials (BOM)

Slide 18

Slide 18 text

@_saulmm Bill of Materials (BOM) / / Firebase 
 
 implementation platform(“com.google.firebase:firebase-bom:26.1.0") 
 implementation "com.google.firebase:firebase-analytics" implementation "com.google.firebase:firebase-perf" implementation "com.google.firebase:firebase-messaging" implementation “com.google.firebase:firebase-config-ktx:19.2.0” implementation "com.google.firebase:firebase-appindexing" implementation 'com.google.firebase:firebase-inappmessaging-display' implementation 'com.google.firebase:firebase-storage' implementation 'com.google.firebase:firebase-crashlytics-ktx' > https://firebase.google.com/docs/android/learn-more#compare-bom-versions

Slide 19

Slide 19 text

@_saulmm #Fri Oct 16 11:09:31 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\: / / services.gradle.org/distributions/gradle-6.5-all.zip Project/gradle/wrapper/gradle-wrapper.properties > >= 5 Bill of Materials (BOM)

Slide 20

Slide 20 text

@_saulmm

Slide 21

Slide 21 text

@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import android.widget.PopupMenu 
 class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) {

Slide 22

Slide 22 text

@_saulmm < ! - - . . . - - > <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Popsy.PopupMenu.Small < / item> <item name="textAppearanceLargePopupMenu">@style/TextAppearance.Popsy.PopupMenu.Large < / item> < ! - - . . . - - > < / style> <style name="TextAppearance.Popsy.PopupMenu.Large" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name=“android:textColor">?colorOnSurface < / item> <item name="android:textSize">14sp < / item> < / style> <style name="TextAppearance.Popsy.PopupMenu.Small" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name=“android:textColor">?colorOnSurface < / item> <item name="android:textSize">12sp < / item> < / style>

Slide 23

Slide 23 text

@_saulmm

Slide 24

Slide 24 text

@_saulmm < ! - - . . . - - > <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Popsy.PopupMenu.Small < / item> <item name="textAppearanceLargePopupMenu">@style/TextAppearance.Popsy.PopupMenu.Large < / item> < ! - - . . . - - > < / style> <style name="TextAppearance.Popsy.PopupMenu.Large" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">14sp < / item> < / style> <style name="TextAppearance.Popsy.PopupMenu.Small" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">12sp < / item> < / style>

Slide 25

Slide 25 text

@_saulmm < ! - - . . . - - > <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.Popsy.PopupMenu.Small < / item> <item name="textAppearanceLargePopupMenu">@style/TextAppearance.Popsy.PopupMenu.Large < / item> < ! - - . . . - - > < / style> <style name="TextAppearance.Popsy.PopupMenu.Large" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">14sp < / item> < / style> <style name="TextAppearance.Popsy.PopupMenu.Small" parent="@style/TextAppearance.Popsy.Body2.Dark"> <item name="android:textColor">@color/red_700 < / item> <item name="android:textSize">12sp < / item> < / style>

Slide 26

Slide 26 text

@_saulmm

Slide 27

Slide 27 text

@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import android.widget.PopupMenu 
 class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) {

Slide 28

Slide 28 text

@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import android.widget.PopupMenu 
 class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) {

Slide 29

Slide 29 text

@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import androidx.appcompat.widget.PopupMenu 
 class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) {

Slide 30

Slide 30 text

@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import androidx.appcompat.widget.PopupMenu 
 class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) {

Slide 31

Slide 31 text

@_saulmm import android.content.Context import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import androidx.appcompat.widget.PopupMenu 
 class QuickMessagesPopup( private val context: Context, anchor: View, ) : PopupMenu( context, anchor ) {

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } }

Slide 35

Slide 35 text

@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } }

Slide 36

Slide 36 text

@_saulmm class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { super.onCreate(savedInstanceState, persistentState) setContentView(R.layout.activity_main) } }

Slide 37

Slide 37 text

@_saulmm How to obtain a vector ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) android.defaultConfig. vectorDrawables. useSupportLibrary = true. ContextCompat.getDrawable(…) ResourcesCompat.getDrawable(…) AppCompatResources.getDrawable(…) VectorDrawableCompat.create(…) API >= 21 API <= 21

Slide 38

Slide 38 text

@_saulmm Go to source .([filename].java:[line]) ([filename].kt:[line])

Slide 39

Slide 39 text

@_saulmm Go to source Log.i("IMPORTANT", "look at my java file .(Hello.java:6)") Log.i("IMPORTANT", "For kotlin too! (MainActivity.kt:20)") .([filename].java:[line]) ([filename].kt:[line])

Slide 40

Slide 40 text

@_saulmm Go to source Log.i("IMPORTANT", "look at my java file .(Hello.java:6)")

Slide 41

Slide 41 text

@_saulmm class UserProfileActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_profile) } }

Slide 42

Slide 42 text

@_saulmm class UserProfileActivity: AppCompatActivity( R.layout.activity_user_profile ) { / / override fun onCreate(savedInstanceState: Bundle?) { / / super.onCreate(savedInstanceState) / / setContentView(R.layout.activity_user_profile) / / } }

Slide 43

Slide 43 text

@_saulmm viewBinding class UserProfileActivity: AppCompatActivity() { lateinit var binding: ActivityUserProfileBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityUserProfileBinding.inflate(layoutInflater) setContentView(binding.root) } }

Slide 44

Slide 44 text

@_saulmm viewBinding class UserProfileActivity: AppCompatActivity() { private val binding by viewBinding( ActivityRequestPhoneBinding : : inflate ) override fun onCreate(savedInstanceState: Bundle?) { super.setContentView(savedInstanceState) setContentView(binding.root) } }

Slide 45

Slide 45 text

@_saulmm viewBinding inline fun AppCompatActivity.viewBinding( crossinline bindingInflater: (LayoutInflater) - > T ): Lazy { return lazy(LazyThreadSafetyMode.NONE) { bindingInflater.invoke(layoutInflater) } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 46

Slide 46 text

@_saulmm viewBinding class UserProfileFragment: Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { super.onCreateView(inflater, container, savedInstanceState) val v = inflater.inflate(fragment_profile, container, false) return v } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 47

Slide 47 text

@_saulmm viewBinding class UserProfileFragment: Fragment(R.layout.fragment_user_profile) { } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 48

Slide 48 text

@_saulmm viewBinding class UserProfileFragment: Fragment() { lateinit var binding: ActivityUserProfileBinding override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { super.onCreateView(inflater, container, savedInstanceState) binding = FragmentUserProfileBinding.inflate(container, false) return binding.root } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 49

Slide 49 text

@_saulmm viewBinding class UserProfileFragment: Fragment(R.layout.fragment_user_profile) { lateinit var binding: FragmentUserProfileBinding override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentUserProfileBinding.bind(view) } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 50

Slide 50 text

@_saulmm viewBinding class UserProfileFragment: Fragment(R.layout.activity_user_profile) { private val binding by viewBinding(FragmentUserProfileBinding : : bind) 
 
 } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 51

Slide 51 text

@_saulmm viewBinding class FragmentViewBindingDelegate( val fragment: Fragment, val viewBindingFactory: (View) - > T ) : ReadOnlyProperty { private var binding: T? = null init { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner - > viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { binding = null } }) } } }) } override fun getValue(thisRef: Fragment, property: KProperty < * > ): T { val binding = binding if (binding ! = null) { return binding } val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 52

Slide 52 text

@_saulmm viewBinding class FragmentViewBindingDelegate( val fragment: Fragment, val viewBindingFactory: (View) - > T ) : ReadOnlyProperty { private var binding: T? = null init { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner - > viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { binding = null } }) } } }) } override fun getValue(thisRef: Fragment, property: KProperty < * > ): T { val binding = binding if (binding ! = null) { return binding } val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 53

Slide 53 text

@_saulmm viewBinding class FragmentViewBindingDelegate( val fragment: Fragment, val viewBindingFactory: (View) - > T ) : ReadOnlyProperty { private var binding: T? = null init { fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner - > viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { binding = null } }) } } }) } override fun getValue(thisRef: Fragment, property: KProperty < * > ): T { val binding = binding if (binding ! = null) { return binding } val lifecycle = fragment.viewLifecycleOwner.lifecycle if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") } return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c

Slide 54

Slide 54 text

@_saulmm binding.recyclerNotifications.isVisible = !isEmpty /** * Setting this property to true sets the visibility to * [View.VISIBLE], false to [View.GONE]. **/ inline var View.isVisible: Boolean get() = visibility == View.VISIBLE set(value) { visibility = if (value) View.VISIBLE else View.GONE } View.isVisible

Slide 55

Slide 55 text

@_saulmm View.isVisible binding.recyclerNotifications.isVisible = !isEmpty /** * Setting this property to true sets the visibility to * [View.VISIBLE], false to [View.GONE]. **/ inline var View.isVisible: Boolean get() = visibility == View.VISIBLE set(value) { visibility = if (value) View.VISIBLE else View.GONE }

Slide 56

Slide 56 text

@_saulmm @+id/

Slide 57

Slide 57 text

@_saulmm @+id/

Slide 58

Slide 58 text

@_saulmm beginDelayedTransition() binding.containerTitle.setOnClickListener {

Slide 59

Slide 59 text

@_saulmm beginDelayedTransition() package android.transition; 
 /** * Convenience method to animate, using the default transition, * to a new scene defined by all changes within the given scene root between * calling this method and the next rendering frame. */ public static void beginDelayedTransition(final ViewGroup sceneRoot) { beginDelayedTransition(sceneRoot, null); }

Slide 60

Slide 60 text

@_saulmm Animate layout changes

Slide 61

Slide 61 text

@_saulmm Animate layout changes

Slide 62

Slide 62 text

@_saulmm Animate layout changes

Slide 63

Slide 63 text

@_saulmm Animate layout changes

Slide 64

Slide 64 text

@_saulmm Animate layout changes

Slide 65

Slide 65 text

@_saulmm

Slide 66

Slide 66 text

@_saulmm ViewStub

Slide 67

Slide 67 text

@_saulmm ViewStub

Slide 68

Slide 68 text

@_saulmm ViewStub

Slide 69

Slide 69 text

@_saulmm ViewStub

Slide 70

Slide 70 text

@_saulmm ViewStub “If you have parts of your UI that do not need to be visible on f i rst 
 launch, don’t in f l ate them” 
 
 Developing for Android, III: 


Slide 71

Slide 71 text

@_saulmm ViewStub private fun onNetworkError() { binding.stubNetwork.viewStub? 
 .isVisible = true }

Slide 72

Slide 72 text

@_saulmm ViewStub private fun onNetworkError() { val view = binding.stubNetwork 
 .viewStub?.inflate() }

Slide 73

Slide 73 text

@_saulmm @tools:sample

Slide 74

Slide 74 text

@_saulmm @tools:sample

Slide 75

Slide 75 text

@_saulmm @tools:sample

Slide 76

Slide 76 text

@_saulmm @tools:sample app/sampledata/listings.json { "data": [ { "listing": "Brand new iPhone" }, { "listing": "Garming 1030 new" }, { "listing": "Toyota Yaris 1.5 hdi 100km" }, { "listing": "Google Home Mini" }, { "listing": "Parrot toy" }, { "listing": "Lawn mower" }, { "listing": "Moving sale" }, { "listing": "Porter cable tools" }, { "listing": "Workout set" } ] }

Slide 77

Slide 77 text

@_saulmm @tools:sample

Slide 78

Slide 78 text

@_saulmm @tools:sample

Slide 79

Slide 79 text

@_saulmm

Slide 80

Slide 80 text

@_saulmm Generate Proguard rule

Slide 81

Slide 81 text

@_saulmm Generate Proguard Rules APK Analyzer can help you see which classes were removed by ProGuard and generate keep rules for them.

Slide 82

Slide 82 text

@_saulmm FragmentContainerView - Improvements on entering / exiting animations - Allow replacing 


Slide 83

Slide 83 text

@_saulmm FragmentContainerView - Improvements on entering / exiting animations - Allow replacing 


Slide 84

Slide 84 text

@_saulmm FragmentContainerView - Improvements on entering / exiting animations - Allow replacing 


Slide 85

Slide 85 text

@_saulmm onBackPressedDispatcher 
 
 activity?.onBackPressedDispatcher?.addCallback(this) { 
 } class MyFragment { 


Slide 86

Slide 86 text

@_saulmm onBackPressedDispatcher 
 
 activity?.onBackPressedDispatcher?.addCallback(this) { 
 } class MyFragment { 


Slide 87

Slide 87 text

@_saulmm Log.i(TAG, "An information log") Log.d(TAG, "A debug log") Log.v(TAG, "A verbose log") Log.e(TAG, "An error log") Log.w(TAG, "A Warning log")

Slide 88

Slide 88 text

@_saulmm Log.i(TAG, "An information log") 2019-12-12 10:55:55.165 D/yea debug log 2019-12-12 10:55:55.165 V/yea: A verbose log 2019-12-12 10:55:55.165 E/yea: An error log 2019-12-12 10:55:55.165 W/yea: A warning log

Slide 89

Slide 89 text

@_saulmm Log.wtf(TAG, “An ultra not expected thing happened")

Slide 90

Slide 90 text

@_saulmm /** * What a Terrible Failure: Report a condition that should never happen. * The error will always be logged at level ASSERT with the call stack. * Depending on system configuration, a report may be added to the * {@link android.os.DropBoxManager} and/or the process may be terminated * immediately with an error dialog. * @param tag Used to identify the source of a log message. * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) {

Slide 91

Slide 91 text

@_saulmm /** * What a Terrible Failure: Report a condition that should never happen. * The error will always be logged at level ASSERT with the call stack. * Depending on system configuration, a report may be added to the * {@link android.os.DropBoxManager} and/or the process may be terminated * immediately with an error dialog. * @param tag Used to identify the source of a log message. * @param msg The message you would like logged. */ public static int wtf(String tag, String msg) {

Slide 92

Slide 92 text

@_saulmm Selection Tracker dependencies { // ... implementation 'androidx.recyclerview:recyclerview-selection:1.0.0' }

Slide 93

Slide 93 text

@_saulmm Log Log.e(TAG, “") != Log.wtf(TAG, "") Could crash your app

Slide 94

Slide 94 text

@_saulmm Selection Tracker dependencies { // ... implementation 'androidx.recyclerview:recyclerview-selection:1.0.0' } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 95

Slide 95 text

@_saulmm Selection Tracker class PoiAdapter: RecyclerView.Adapter() { MainActivity.PO_SELECTION_KEY, recyclerView, StableIdKeyProvider(recyclerView), PoiDetailsLookUp(recyclerView), StorageStrategy.createLongStorage() ).build() lateinit var tracker: SelectionTracker https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 96

Slide 96 text

@_saulmm Selection Tracker class PoiDetailsLookUp(val recyclerView: RecyclerView) : ItemDetailsLookup() { 
 override fun getItemDetails(e: MotionEvent): ItemDetails? { val view = recyclerView.findChildViewUnder(e.x, e.y) return if (view != null) { (recyclerView.getChildViewHolder(view) as PoiViewHolder) 
 .itemDetails() } else { null } } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 97

Slide 97 text

@_saulmm Selection Tracker class PoiViewHolder(val binding: ItemRowBinding): 
 RecyclerView.ViewHolder(binding.root) { 
 fun bind(item: Poi, isActivated: Boolean) { binding.poi = item binding.root.isActivated = isActivated } fun itemDetails(): ItemDetailsLookup.ItemDetails = object : ItemDetailsLookup.ItemDetails() { override fun getSelectionKey(): Long? = itemId override fun getPosition(): Int = adapterPosition } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 98

Slide 98 text

@_saulmm Selection Tracker class PoiViewHolder(val binding: ItemRowBinding): 
 RecyclerView.ViewHolder(binding.root) { 
 fun bind(item: Poi, isActivated: Boolean) { binding.poi = item binding.root.isActivated = isActivated } fun itemDetails(): ItemDetailsLookup.ItemDetails = object : ItemDetailsLookup.ItemDetails() { override fun getSelectionKey(): Long? = itemId override fun getPosition(): Int = adapterPosition } } https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 99

Slide 99 text

@_saulmm Selection Tracker fun addSelectionObserver(selectionObserver 
 : SelectionTracker.SelectionObserver) { tracker.addObserver(selectionObserver) } poiAdapter.addSelectionObserver(object 
 : SelectionTracker.SelectionObserver() { 
 override fun onSelectionChanged() { super.onSelectionChanged() val count = poiAdapter.tracker.selection.size() 
 val hasSelection = !poiAdapter.tracker.selection.isEmpty // Start action mode here } 
 }) https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Slide 100

Slide 100 text

@_saulmm Gradle abbreviations ➜ ./gradlew assembleDev > Task :app:assembleDev

Slide 101

Slide 101 text

@_saulmm Gradle abbreviations ➜ ./gradlew assembleDev ➜ ./gradlew aD

Slide 102

Slide 102 text

@_saulmm Gradle abbreviations ➜ ./gradlew assembleDev ➜ ./gradlew aD

Slide 103

Slide 103 text

@_saulmm Enums in Android “Think of it this way, enums 
 are kind of like gremlins, right?” 
 
 “The price of ENUMS” 3:10 


Slide 104

Slide 104 text

@_saulmm Enums in Android “Think of it this way, enums 
 are kind of like gremlins, right?” 
 
 “The price of ENUMS” 3:10 


Slide 105

Slide 105 text

@_saulmm Enums in Android Proguard and R8 can optimise simple Java and Kotlin enums into ints for you

Slide 106

Slide 106 text

@_saulmm Enums in Android -optimizations class/unboxing/enum Proguard and R8 can optimise simple Java and Kotlin enums into ints for you

Slide 107

Slide 107 text

@_saulmm Enums in Android

Slide 108

Slide 108 text

@_saulmm Enums in Android

Slide 109

Slide 109 text

Graciñas @_saulmm [email protected]