Upgrade to Pro — share decks privately, control downloads, hide ads and more …

10 years of Android Development: The Retrospective

10 years of Android Development: The Retrospective

Working in the Android Development since 2011 when I made my first apps on a Galaxy S with Froyo (2.2) and there was no Fragment, Holo design was beautiful and ActionBarSherlock was the big thing!

Let's see how Android Development has evolved during this 10-years timeframe with the rise of Fragments, Material design, and now Kotlin and Jetpack Compose with fun anecdotes and code samples.

Julien Salvi

October 07, 2022
Tweet

More Decks by Julien Salvi

Other Decks in Programming

Transcript

  1. 10 years of Android development Julien Salvi - Android GDE

    | Lead Android Engineer @ Aircall @JulienSalvi The Retrospective
  2. ⚠ 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 😅)
  3. 10+ years of Android development A little bit of context…

    A little bit of context… A little bit of Context
  4. 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…)
  5. 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
  6. 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
  7. 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 🚀
  8. 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
  9. 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 version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" style="@style/AppTheme" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/eperion_background" android:orientation="vertical" android:padding="50dip" > <ScrollView android:id="@+id/scroller" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center"> <Button android:id="@+id/pickPicture" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Add a photo to the Gallery" android:textColor="#aa0000"/> <Button android:id="@+id/openGalleryButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Open your gallery" android:layout_marginTop="10dp" android:textColor="#aa0000"/> </LinearLayout> </ScrollView> </LinearLayout>
  10. 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 <AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_x="100px" android:layout_y="300px" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_x="120px" android:layout_y="350px" /> </AbsoluteLayout>
  11. 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
  12. 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
  13. 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); } }
  14. 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()); } }
  15. 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 }
  16. 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
  17. 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
  18. 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 <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewmodel" type="com.myapp.data.ViewModel" /> </data> <ConstraintLayout> <TextView android:text="@{viewmodel.userName}" /> <ConstraintLayout/> </layout> findViewById<TextView>(R.id.sample_text).apply { text = viewModel.userName }
  19. 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!") ) }
  20. 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
  21. 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<String> xmlCountries = null; private Set<String> xmlMusicStyle = null; private MusicGroupDBAdapter musicDatabase = null; private ConcertListAdapter concertAdapter = null; private ArrayList<MusicGroup> 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<String>(); xmlCountries = new HashSet<String>(); //******************************************** //****** Setting up the custom list ********** //******************************************** List<MusicGroup> groupItems = new ArrayList<MusicGroup>(); 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
  22. 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…
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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);
  29. 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<String> future = executorService.submit(callableTask); String result = null; try { result = future.get(); // Blocking get() method } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } // Cancel tasks executorService.shutdown();
  30. 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
  31. 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<String, Integer, String> { 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
  32. 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 }
  33. 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<ArrayList>() { @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 } });
  34. 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
  35. 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 */ } }
  36. 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! 🤔
  37. 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?
  38. 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 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"/> <ScrollView/> LinearLayout container = findViewById(R.id.container); container.addView(View(context))
  39. 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 } } );
  40. 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. } });
  41. 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
  42. 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 <ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> 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 🙈)
  43. 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 <androidx.recyclerview.widget.RecyclerView android:id="@+id/myRecyclerView" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> val adapter = CallQualityAdapterSample() adapter.submitList(listOf(Things)) binding.myRecyclerView.adapter = adapter class MyAdapter() : ListAdapter<Thing, MyAdapter.BaseViewHolder>(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 } } }
  44. 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(), ) } }
  45. 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! 🤔
  46. 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
  47. Permissions • Since API 1 • Allow the app to

    access sensible features or data. • Declare them in the manifest… Y(HOLO) age <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.plop"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <manifest/>
  48. 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 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.plop"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <manifest/>
  49. 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 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.plop"> <!-- Protection level "normal" no request at runtime --> <uses-permission android:name="android.permission.INTERNET" /> <!-- "Dangerous" permission, must be granted by the user.--> <uses-permission android:name="android.permission.READ_CONTACTS" /> <manifest/>
  50. 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! }
  51. 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); } }
  52. 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 ) } }
  53. 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
  54. 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! 🤔
  55. 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