Slide 1

Slide 1 text

10 years of Android development Julien Salvi - Android GDE | Lead Android Engineer @ Aircall @JulienSalvi The Retrospective

Slide 2

Slide 2 text

Julien Salvi Lead Android Engineer @ Aircall Android GDE PAUG, Punk & IPAs! @JulienSalvi Bonjour !

Slide 3

Slide 3 text

⚠ Disclaimer ⚠ ● Android (obviously 😅) ● My personal experience as a developer ● How Android dev has evolved ● Android dev samples! ● This talk will be interactive (and fun 🥸) ● How Android was born (Chet Haase did it perfectly 😅) ● ALL the things that happened in Android Dev (it would require more than 50min 😅)

Slide 4

Slide 4 text

10+ years of Android development A little bit of context… A little bit of context… A little bit of Context

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Startups

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Android Dev timeline 2020 2008 2022 2018 2016 2014 2010 2012 ActionBarSherlock Android 1.0 Eclipse + ADT + Java I/O 2019: Kotlin first, Rise of Compose Android 3.0 ActionBar, Fragment, Holo Design, support libraries, AndroidAnnotation Android Studio 1.0 Gradle build tools Android 5.0 Material Design, more support libs, Google TV, VR Cardboard, ActionBarSherlock ☠ AsyncTask Android 1.5 Android 6.0 Runtime permissions, Doze mode, Apache HTTP client removal Chromecast AsyncTask ☠ Support Lib ☠ Welcome androidx Android 9 Android 11 Android 13 Compose 1.0 Daydream VR ☠ KMM Beta Volley ButterKnife Dagger Retrofit Picasso RxJava Kotlin Architecture Components (ViewModel, Livedata…)

Slide 16

Slide 16 text

Android Dev timeline 2020 2008 2022 2018 2016 2014 2010 2012 ActionBarSherlock Android 1.0 Eclipse + ADT + Java I/O 2019: Kotlin first, Rise of Compose Android 3.0 ActionBar, Fragment, Holo Design, support libraries, AndroidAnnotation Android Studio 1.0 Gradle build tools Android 5.0 Material Design, more support libs, Google TV, VR Cardboard, ActionBarSherlock ☠ AsyncTask Android 1.5 Android 6.0 Runtime permissions, Doze mode, Apache HTTP client removal Chromecast AsyncTask ☠ Support Lib ☠ Welcome androidx Android 9 Android 11 Android 13 Compose 1.0 Daydream VR ☠ KMM Beta Volley ButterKnife Dagger Retrofit Picasso RxJava Kotlin Architecture Components (ViewModel, Livedata…) Y(HOLO) age Material age K-age

Slide 17

Slide 17 text

2020 2008 2022 2018 2016 2014 2010 2012 Android 1.0 Android 3.0 Android 5.0 Android 1.5 Android 6.0 Android 9 Android 11 Android 13 Y(HOLO) age Material age K-age ActionBar Build native UI Network calls Architecture Permissions Background work Scrolling content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Build native UI Here is my point of view…

Slide 21

Slide 21 text

Build native UI From the first View components to Compose How the community contributed to a better developer experience How the developer experience has evolved over time? Here is the long journey to the Compose UI: from XML/Java to Compose/Kotlin 🚀

Slide 22

Slide 22 text

Build native UI ● Not many components with API 1 but many possibilities… by hand ● Activity, View, ViewGroup, LinearLayout, FrameLayout, AbsoluteLayout… Y(HOLO) age The Dark Age // XML, Activity and findViewById public class ReecoBotLauncher extends Activity { private Button vButton; private RadioButton rdb1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_reeco_bot_launcher); vButton = (Button) findViewById(R.id.valide_mode); rdb1 = (RadioButton) findViewById(R.id.rb1); } } // CustomView public class LabyrinthView extends SurfaceView { public LabyrinthView(Context context) { super(context); // TODO Auto-generated constructor stub init(); } public LabyrinthView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub init(); } public LabyrinthView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub init(); } } https://github.com/Oleur/NXTReecoBot

Slide 23

Slide 23 text

Build native UI ● Not many components with API 1 but many possibilities… by hand ● Activity, View, ViewGroup, LinearLayout, FrameLayout, AbsoluteLayout… Y(HOLO) age The Dark Age

Slide 24

Slide 24 text

Build native UI ● Not many components with API 1 ● Activity, View, ViewGroup, LinearLayout, FrameLayout, AbsoluteLayout… ● AbsoluteLayout! Deprecated directly in API 3 😅 ● Do not do that at home 😆 Y(HOLO) age The Dark Age

Slide 25

Slide 25 text

Build native UI ● Supporting multiple density wasn’t trivial. ● Multiple assets for different densities: ldpi, mdpi, hdpi, xhdpi ● No SVG back then… but 9patch! Y(HOLO) age The Dark Age

Slide 26

Slide 26 text

Build native UI ● Honeycomb (Android 3.0) introduces Holo Design ● New design, new components! ● Rise of the Fragment! And support libraries v4! ● Some kind of a design system! Y(HOLO) age The Holo Age

Slide 27

Slide 27 text

Build native UI ● More support libraries from Google with support v7 ● Android community is rising: ButterKnife, AndroidAnnotation, ActionBarSherlock, EventBus… ● Developer experience 📈 Y(HOLO) age The Holo Age https://github.com/JakeWharton/butterknife class ExampleActivity extends Activity { @BindView(R.id.user) EditText username; @BindView(R.id.pass) EditText password; @BindString(R.string.login_error) String loginErrorMessage; @OnClick(R.id.submit) void submit() { // TODO Click click click } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); } }

Slide 28

Slide 28 text

Build native UI ● More support libraries from Google with support v7 ● Android community is rising: ButterKnife, AndroidAnnotation, ActionBarSherlock, EventBus… ● Developer experience 📈 Y(HOLO) age The Holo Age http://androidannotations.org/ @Fullscreen @EActivity(R.layout.bookmarks) @WindowFeature(Window.FEATURE_NO_TITLE) public class BookmarksToClipboardActivity extends Activity { BookmarkAdapter adapter; @ViewById ListView bookmarkList; @ViewById EditText search; @RestService BookmarkClient restClient; @AnimationRes Animation fadeIn; @AfterViews void initBookmarkList() { adapter = new BookmarkAdapter(this); bookmarkList.setAdapter(adapter); } @Click({R.id.updateBookmarksButton1, R.id.updateBookmarksButton2}) void updateBookmarksClicked() { searchAsync(search.getText().toString(), application.getUserId()); } @ItemClick void bookmarkListItemClicked(Bookmark selectedBookmark) { clipboardManager.setText(selectedBookmark.getUrl()); } }

Slide 29

Slide 29 text

Build native UI ● More support libraries from Google with support v7 ● Android community is rising: ButterKnife, AndroidAnnotation, ActionBarSherlock, EventBus… ● Developer experience 📈 Y(HOLO) age The Holo Age https://github.com/greenrobot/EventBus public static class MessageEvent { } EventBus.getDefault().post(new MessageEvent()); @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { // Do something }

Slide 30

Slide 30 text

Build native UI ● After Material was annonce, lots of backward components were released by Google ● Golden age for support v7: AppCompat, CardView, RecyclerView, Leanback… ● New concept: elevation! The Material Age https://developer.android.com/develop/ui/views/theming/look-and-feel Material age

Slide 31

Slide 31 text

Build native UI ● Google pushed more and more support libraries ● ConstraintLayout, Arch Components and Design lib ● Developers have many capabilities to produce high quality apps The Golden Material Age https://developer.android.com/codelabs/constraint-layout#0 Material age

Slide 32

Slide 32 text

Build native UI ● We love fragmentation! Support libs are dead! Long live the androidx libraries! ● Google invested in a lot in DevExp: some was nice, some not (DataBinding 🥲) ● Moving to Kotlin helped a lot! The Rise of Kotlin https://developer.android.com/topic/libraries/data-binding K-age findViewById(R.id.sample_text).apply { text = viewModel.userName }

Slide 33

Slide 33 text

Build native UI ● Compose was game changing for the UI development on Android ● New paradigms, new concepts! ● XML sunset… full Kotlin ahead! ● Efficient UI code 🤓 The Compose Age https://developer.android.com/jetpack/compose/tutorial K-age class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Colleague", “Jetpack Compose is great!") ) }

Slide 34

Slide 34 text

Build native UI Compose is still “new” Move away fromKotlin synthetics Move to Compose Tooling to build views is better and better now Go 😎 Think! 🤔 Explore 🤓 Stop using Data Binding Never forget the View system Time to say goodbye to XML The community is building great stuff

Slide 35

Slide 35 text

Architectures One does not simply…

Slide 36

Slide 36 text

Architecture ● All In The Activity All In The Fragment ● Back in 2010 when I was a student or sometimes now when doing POCs 🙈 ● Please don’t do that! 😅 AITA or AITF package com.fimu.fragments; chronological order. * @author Julien Salvi */ public class ConcertList extends SherlockFragment implements OnClickListener, OnItemClickListener, OnItemLongClickListener { private ListView concerts; private ImageButton openGMap; private ImageButton shareFB; private TextView textNbConcert; private View adView = null; private AutoCompleteTextView groupNameAutoComplete = null; private Spinner spinnerCountry = null; private Spinner spinnerStyle = null; private Spinner spinnerResults = null; private Button buttonSearch = null; private static Document xmlDoc = null; public static final String PREFS_NAME = "prefsFile"; public SharedPreferences prefs = null; private int nbConcert = 0; private Set xmlCountries = null; private Set xmlMusicStyle = null; private MusicGroupDBAdapter musicDatabase = null; private ConcertListAdapter concertAdapter = null; private ArrayList allMusicGroups = null; //private NotificationService notifService = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActivity().setContentView(R.layout.activity_concert_list); this.setHasOptionsMenu(true); //Listview which contains the selected concerts. concerts = (ListView) getActivity().findViewById(R.id.listview_concerts); musicDatabase = new MusicGroupDBAdapter(getActivity()); //Buttons for facebook and google map opening. openGMap = (ImageButton) getActivity().findViewById(R.id.button_gmaps); shareFB = (ImageButton) getActivity().findViewById(R.id.button_share_facebook); openGMap.setOnClickListener(this); shareFB.setOnClickListener(this); openGMap.setVisibility(View.INVISIBLE); shareFB.setVisibility(View.INVISIBLE); textNbConcert = (TextView) getActivity().findViewById(R.id.text_nb_concert); Typeface tf = Typeface.createFromAsset(getActivity().getAssets(), "fonts/TravelingTypewriter.otf"); textNbConcert.setTypeface(tf); //Typeface tfTitle = Typeface.createFromAsset(getAssets(), "fonts/PWScolarpaper.ttf"); //Init the sets: xmlMusicStyle = new HashSet(); xmlCountries = new HashSet(); //******************************************** //****** Setting up the custom list ********** //******************************************** List groupItems = new ArrayList(); getActivity(); prefs = getActivity().getSharedPreferences(PREFS_NAME, FragmentActivity.MODE_PRIVATE); nbConcert = prefs.getInt("NB_CONCERTS", 0); if (nbConcert == 0) { textNbConcert.setText(R.string.no_concert); openGMap.setEnabled(false); shareFB.setEnabled(false); } else { openGMap.setEnabled(true); shareFB.setEnabled(true); musicDatabase.open(); allMusicGroups = musicDatabase.getAllMusicGroupOrderByTime(); int dataSize = allMusicGroups.size(); textNbConcert.setText(nbConcert+" "+getString(R.string.nb_concert)); //Adding the items into the custom list. for (int i=0; i < dataSize ;i++) { musicDatabase.open(); MusicGroup group = allMusicGroups.get(i); musicDatabase.close(); groupItems.add(i, new MusicGroup(group.getId(), group.getGroupName(), group.getMusicStyle(), group.getScene(), group.getCountry(), group.getDate(), group.getHour())); /*long dateMillis = System.currentTimeMillis(); try { SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); Date date = format.parse(group.getDate()+" "+group.getHour()+":00"); dateMillis = (date.getTime()-(5*60*1000)); } catch (ParseException e) { e.printStackTrace(); }*/ //Service to receive the notifications. //notifService = new NotificationService(this, dateMillis) /*Intent serviceIntent = new Intent(this, NotificationService.class); serviceIntent.putExtra("concertTime", dateMillis); startService(serviceIntent);*/ } musicDatabase.close(); } XMLFimuParser parser = new XMLFimuParser(getActivity()); xmlDoc = parser.getLocalXMLDocument("xml/fimu.xml"); concertAdapter = new ConcertListAdapter(getActivity(), groupItems); concerts.setAdapter(concertAdapter); concerts.setClickable(true); concerts.setOnItemClickListener(this); concerts.setOnItemLongClickListener(this); } @Override p….. Y(HOLO) age

Slide 37

Slide 37 text

Architecture ● Model-View-Controller ● Separation of concerns ● User inputs are handled by the controller ● Do not put business logic in View components MVC Y(HOLO) age https://medium.com/upday-devs/android-architecture-patterns-part-1-model-view-controller-3baecef5f2b6 Model View Controller Activity EditText, Button…

Slide 38

Slide 38 text

Architecture ● Model-View-Presenter ● Separation of concerns, abstraction ● User inputs handled by the View ● Do not put business logic in View components MVP https://www.raywenderlich.com/7026-getting-started-with-mvp-model-view-presenter-on-android Model View Presenter Activity, Fragment, EditText… Data Manager Y(HOLO) age Material age

Slide 39

Slide 39 text

Architecture ● Model-View-View Model ● Separation of concerns, abstraction ● Google guide for app architecture ● Architecture libraries from Google since 2017: ViewModel, LiveData… MVVM https://developer.android.com/topic/architecture Model View ViewModel Activity, Fragment, EditText… Data Layer: Repository, DataSource Material age K-age UI observe changed User inputs androidx ViewModel Fetch data VM Observe data changes

Slide 40

Slide 40 text

Architecture ● Abstract the data layer ● Separation of concerns ● Single source of truth Repository pattern https://developer.android.com/topic/architecture/data-layer Material age K-age

Slide 41

Slide 41 text

Architecture ● Well Clean-ish Architecture ● Separation of concerns, abstraction, testable code ● ❗Do not follow these concepts by the book: take inspiration and what’s work for your app! Clean Architecture https://antonioleiva.com/clean-architecture-android/ Material age K-age

Slide 42

Slide 42 text

Architecture ● Model-View-What You Want ● Separation of concerns, abstraction, maintainable and testable ● Do not put business logic in View components MVWYW Y(HOLO) age Material age K-age

Slide 43

Slide 43 text

IT DEPENDS 🤷 Go 😎 Explore 🤓 Architecture Think! 🤔

Slide 44

Slide 44 text

Background work Full story in the thread 🧵

Slide 45

Slide 45 text

Background work ● Available since API 1 as core to Java ● Basic Java threading tools. ● We have to make them aware of the app lifecycle Thread, Runnable Y(HOLO) age // Java Thread class PrimeThread extends Thread { PrimeThread() { } public void run() { // Compute stuff } } PrimeThread p = new PrimeThread(); p.start(); // Using a Runnable class MyRun implements Runnable { PrimeRun() { } public void run() { // compute stuff } } MyRun p = new MyRun(); new Thread(p).start(); https://developer.android.com/reference/java/lang/Thread // Access the UI thread from the background thread Activity.runOnUiThread(Runnable)); View.post(Runnable); View.postDelayed(Runnable, long);

Slide 46

Slide 46 text

Background work ● Available since API 1, part of Java Util. Take and execute a Runnable ● Have useful factory and utility methods ● More configuration options than Java Thread Executors Y(HOLO) age https://developer.android.com/reference/java/util/concurrent/Executors ExecutorService executor = Executors.newFixedThreadPool(10); // execute a runnableTask executorService.execute(runnableTask); // Future excecution Future future = executorService.submit(callableTask); String result = null; try { result = future.get(); // Blocking get() method } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } // Cancel tasks executorService.shutdown();

Slide 47

Slide 47 text

Background work ● Available since API 1, part of the Android framework. ● Perform long-running operation ● IntentService appears in API 3 but now deprecated ● More restrictions has come over time Services https://developer.android.com/guide/components/services public class BackgroundService extends Service { //on start command method will be called for starting our service. @Override public int onStartCommand(Intent intent, int flags, int startId) { // Do long running stuff here return START_STICKY; } //on destroy method will be called when the service is destroyed. @Override public void onDestroy() { super.onDestroy(); } @Override public IBinder onBind(Intent intent) { return null; } } Y(HOLO) age Material age K-age

Slide 48

Slide 48 text

Background work ● Available since API 3 but (finally) deprecated in API 30 ● Can leak across config change ● Not attached to a lifecycle ● Don't use them! AsyncTask https://developer.android.com/reference/android/os/AsyncTask Y(HOLO) age private class BackgroundTask extends AsyncTask { protected String doInBackground(String... stuff) { // Publish progress at some point publishProgress(...); return result; } protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } protected void onPostExecute(String result) { // Do something with the result } } new BackgroundTask().execute("stuff", "otherStuff"); Material age

Slide 49

Slide 49 text

Background work ● Available since API 3 but (finally) deprecated in API 30 ● Can leak across config change ● Not attached to a lifecycle ● Don't use them! AsyncTask http://androidannotations.org/ Y(HOLO) age Material age // With AndroidAnnotation @Background void searchAsync() { // do background work String stuff = getStuff() updateBookmarks(stuff); } @UiThread void updateUI(String stuff) { // update the UI }

Slide 50

Slide 50 text

Background work ● Compat with API 9 for RxJava 1 or API 21 for RxJava 3 ● No callbacks and linear logic to launch background task easily ● Non trivial learning, temptation to Rx all the things, debugging is meh, needs tricks to be lifecycle aware RxJava https://github.com/ReactiveX/RxJava Y(HOLO) age Material age Observable.just(stuff) // Chain operations (map, zip, conflate…) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(new Observer() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(ArrayList arrayList) { // Do something with the result } @Override public void onError(Throwable e) { // Error handling made simple } @Override public void onComplete() { //cleaning up tasks } });

Slide 51

Slide 51 text

Background work ● Available since 2019 (API 14+), part of the Jetpack libraries ● Recommended solution for persistent work: immediate, deferrable and long running. ● Robust scheduling and many configuration options WorkManager https://developer.android.com/topic/libraries/architecture/workmanager class MyWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { //do the work you want done in the background here return Result.success() } } // optionally, add constraints like power, network availability val constraints: Constraints = Constraints.Builder() .setRequiresCharging(true) .setRequiredNetworkType(NetworkType.CONNECTED) .build() val myWork = OneTimeWorkRequestBuilder() .setConstraints(constraints).build() Material age K-age

Slide 52

Slide 52 text

Background work ● Kotlin API since 2018 ● Take advantage of Kotlin to have a solid and robust threading system with Scope, Job and Supervisor ● Lightweight, fewer memory leaks, cancellation support and Jetpack integration Coroutine https://github.com/Kotlin/kotlinx.coroutines Material age K-age suspend fun fetchDocs() { val result = get("developer.android.com") show(result) } suspend fun get(url: String) = withContext(Dispatchers.IO) { /* perform network IO here */ } }

Slide 53

Slide 53 text

Go 😎 Explore 🤓 Forget AsyncTask! It’s even deprecated 😅 Multithreading is not easy! Services are more strict but useful Use Kotlin ? Go with Coroutine Not a fan of RxJava (sorry 😅) Explore Kotlin Flow for reactive apps WorkManager Background work Think! 🤔

Slide 54

Slide 54 text

Scrolling content They see me scrolling…

Slide 55

Slide 55 text

Scrolling content Fundamental on mobile UX Components since API 1: ScrollView, ListView. Allows (in)finite scrolling content. How easy and performant are the scrolling widgets on Android? How these components evolved over time?

Slide 56

Slide 56 text

Scrolling content ● Available since API 1 ● Only one direct child in the ScrollView ● Add views to your container ● And that’s it ^^ no scroll listener back then… well not directly Y(HOLO) age ScrollView LinearLayout container = findViewById(R.id.container); container.addView(View(context))

Slide 57

Slide 57 text

Scrolling content ● Available since API 1 ● Only one direct child in the ScrollView ● Add views to your container ● And that’s it ^^ no scroll listener back then… well not directly Y(HOLO) age ScrollView public class MyScrollView extends ScrollView { @Override protected void onScrollChanged( int l, int t, int oldl, int oldt) { // Do the trick here and expose the data with a listener } } // OR scrollView .getViewTreeObserver() .addOnScrollChangedListener( new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { // do something when scrolling } } );

Slide 58

Slide 58 text

Scrolling content ● Scroll listener officially supported with API 23. ● Get scroll offset directly from the ScrollView ● Then backported with NestedScrollView ScrollView Material age scrollView.setOnScrollChangeListener( new View.OnScrollChangeListener() { @Override public void onScrollChange( View view, int x, int y, int oldX, int oldY ) { // Do what you want with the offsets. } });

Slide 59

Slide 59 text

Scrolling content ● Compose allows you to make a container scrollable very easily ● Use the Modifier .verticalScroll() or .horizontalScroll() ● Quick setup & support nested scrolling 🔥 Compose https://developer.android.com/jetpack/compose/gestures#scroll-modifiers @Composable fun ScrollBoxes() { Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .verticalScroll(rememberScrollState()) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } } K-age

Slide 60

Slide 60 text

Scrolling content ● Available since API 1 ● Infinite & efficient scroll container ● Lots of boilerplate… like A LOT! ● It was so painful to swipe, animate or get the scroll position or impl. ViewHolder pattern! Y(HOLO) age ListView https://developer.android.com/reference/android/widget/ListView private class MyAdapter extends BaseAdapter { // override other abstract methods here @Override public View getView( int position, View convertView, ViewGroup container) { if (convertView == null) { convertView = getLayoutInflater().inflate(R.layout.item, container, false); } ((TextView) convertView.findViewById(R.id.text)) .setText(getItem(position)); return convertView; } } simpleList = (ListView) findViewById(R.id.list); MyAdapter mAdapter = new MyAdapter(context); mAdapter.setList(mList) simpleList.setAdapter(mAdapter); // Dispatch new data set mAdapter.setList(mNewList) mAdapter.notifyDataSetChanged() (And I am not talking about GridView 🙈)

Slide 61

Slide 61 text

Scrolling content ● Released in 2014 with the support library v21 ● Nice tooling, more flexible & performant than ListView to display large set of data ● Horizontal and Vertical scrolling! ● But still lots of boilerplate… RecyclerView https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/RecyclerView Material age val adapter = CallQualityAdapterSample() adapter.submitList(listOf(Things)) binding.myRecyclerView.adapter = adapter class MyAdapter() : ListAdapter(DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ThingItemBinding.inflate(inflater, parent, false) return ThingViewHolder(binding) } override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { val element = getItem(position) holder.bind(element) } abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { abstract fun bind(item: Thing) } inner class ThingViewHolder( private val binding: ThingItemBinding ) : BaseViewHolder(binding.root) { override fun bind(item: Thing) { // Bind the view with the data } } }

Slide 62

Slide 62 text

Scrolling content ● Compose introduced LazyColum, LazyRow and LazyVerticalGrid. ● No more XML, no more Adapter… Wesa freeee! ● Performant but still some improvements to do 💪 Lazy lists with Compose https://developer.android.com/jetpack/compose/lists#lazy K-age LazyColumn( verticalArrangement = Arrangement.spacedBy(4.dp), ) { // Add a single item item { Text( text = "First item", modifier = Modifier.animateItemPlacement(), ) } // Add 5 items items(5) { index -> Text( text = "Item: $index", modifier = Modifier.animateItemPlacement(), ) } }

Slide 63

Slide 63 text

Keep going with RecyclerView if you stick to the View system Using ListView Adapter can become very complicated 😅 Use Compose Lazy lists Move to Compose Animation with RecyclerView items was never straightforward Go 😎 Explore 🤓 Scrolling content Think! 🤔

Slide 64

Slide 64 text

Permissions Granted!

Slide 65

Slide 65 text

Permissions ● Since API 1 ● Allow the app to access sensible features or data. ● Declare them in the manifest ● The most famous permission… Y(HOLO) age https://twitter.com/seyedjafariy/status/1517902894925762565

Slide 66

Slide 66 text

Permissions ● Since API 1 ● Allow the app to access sensible features or data. ● Declare them in the manifest… Y(HOLO) age

Slide 67

Slide 67 text

Permissions ● Since API 1 ● Allow the app to access sensible features or data. ● Declare them in the manifest… ● …but the user had no power to reject them 🙀 Y(HOLO) age

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

Permissions ● The open-bar ended with API 23 ● Permissions must be granted by the user to use the sensible features. ● Permissions level: normal, dangerous, signature, development… ● Ask the permissions at runtime! Material age

Slide 70

Slide 70 text

Permissions ● The open-bar ended with API 23 ● Permissions must be granted by the user to use the sensible features. ● Permissions level: normal, dangerous, signature, development… ● Ask the permissions at runtime! Material age // Check and request permission to the user String permision = Manifest.permission.READ_CONTACTS; int requestCode = 1; if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale( this, permission)) { // Please grant the requested permission ActivityCompat.requestPermissions( this, new String[]{permission}, requestCode); } else { ActivityCompat.requestPermissions( this, new String[]{permission}, requestCode); } } else { // Granted! Good to go! }

Slide 71

Slide 71 text

Permissions ● The open-bar ended with API 23 ● Permissions must be granted by the user to use the sensible features. ● Permissions level: normal, dangerous, signature, development… ● Ask the permissions at runtime! Material age // In your Activity @Override public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults ) { switch (requestCode) { case 1: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { //permission with request code 1 granted } else { //permission with request code 1 was not granted } break; default: super.onRequestPermissionsResult( requestCode, permissions, grantResults); } }

Slide 72

Slide 72 text

Permissions ● More and more restrictions came over time! ● Focus on privacy and security. ● Some “normal” permissions were deprecated or set to “dangerous” ● Old APIs are deprecated! New androidx API! K-age // Register the permission callback. val requestPermissionLauncher = registerForActivityResult(RequestPermission()) { granted -> if (isGranted) { // Permission is granted. } else { // Not granted, warn the user. } } // Check and ask the permission when { ContextCompat.checkSelfPermission( context, Manifest.permission.READ_CONTACTS ) == PackageManager.PERMISSION_GRANTED -> { // Your are good to go 🚀 } shouldShowRequestPermissionRationale(...) -> { // Show something to explain to the user // why your app requires this permission } else -> { // You can directly ask for the permission. requestPermissionLauncher.launch( Manifest.permission.READ_CONTACTS ) } }

Slide 73

Slide 73 text

Permissions ● Permissions have evolved outside the app ● Users can disable them from device settings ● Starting with Android 11, some permission can be temporary granted and unused apps can reset the permissions K-age

Slide 74

Slide 74 text

Being user & security centric Using random permission in your app Forget INTERNET permission 😅 Looking at the permissions of 3rd party libs Be careful with the permissions you use New permission APIs isn’t optimal yet Go 😎 Explore 🤓 Permissions Think! 🤔

Slide 75

Slide 75 text

To have a glimpse of the past… Travel back in time https://web.archive.org Android Google source https://android.googlesource.com/ Android Dev Google Blog https://android-developers.googleblog.com/ Android History https://twitter.com/Android_History

Slide 76

Slide 76 text

Grazie mille 🤌 Julien Salvi - Android GDE @JulienSalvi Have fun with the droid!