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

April 28, 2023
Tweet

More Decks by Julien Salvi

Other Decks in Technology

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 45min 😅)
  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 Development Env.
  7. ActionBar • At the beginning, we had the AppBar. •

    Displaying app or Activity name. • Not some many things to customize Y(HOLO) age
  8. ActionBar • Starting with Android 3 and Holo Design: ActionBar.

    • Title, Menu or home icon customization from an Activity. • No backward compatibility 🥲 Y(HOLO) age Birth of the Action Bar <application ... android:theme="@android:style/Theme.Holo.Light"> <!-- Removing the action bar --> <activity android:theme="@android:style/Theme.Holo.NoActionBar"/> <application/>
  9. ActionBar • Starting with Android 3 and Holo Design: ActionBar.

    • Title, Menu or home icon customization from an Activity. • No backward compatibility 🥲 Y(HOLO) age Birth of the Action Bar // Get the action bar ActionBar actionBar = getActionBar(); // Play with the action bar visibility actionBar.hide(); actionBar.show(); // Home button actionBar.setHomeButtonEnabled(true); actionBar.setDisplayHomeAsUpEnabled(true); // Title and subtitle actionBar.setTitle("Title"); actionBar.setSubtitle("Subtitle"); // Make an actionbar layout and load it like so @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_activity, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: ... default: ... } }
  10. ActionBar • Starting with Android 3 and Holo Design: ActionBar.

    • Title, Menu or home icon customization from an Activity. • No backward compatibility 🥲 Y(HOLO) age Birth of the Action Bar https://android-developers.googleblog.com/2012/01/holo-everywhere.html
  11. ActionBar • Android Community is Awesome! To have an ActionBar

    below Android 3.0 : ActionBarSherlock • Mirror all ActionBar APIs. • Made by Jake Wharton Y(HOLO) age No sh*t Sherlock! <activity android:label="@string/activity_name" android:name=".RandomActivity" android:theme="@style/Theme.Sherlock"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>
  12. ActionBar • Android Community is Awesome! To have an ActionBar

    below Android 3.0 : ActionBarSherlock • Mirror all ActionBar APIs. • Made by Jake Wharton Y(HOLO) age No sh*t Sherlock! import com.actionbarsherlock.app.SherlockActivity; public class RandomActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle("Title"); getSupportActionBar().setBackgroundDrawable(...); setSupportProgressBarIndeterminateVisibility(true); setSupportProgressBarVisibility(true); } }
  13. ActionBar • Android Community is Awesome! To have an ActionBar

    backport: ActionBarSherlock • Mirror all ActionBar APIs. • Made by Jake Wharton Y(HOLO) age No sh*t Sherlock! http://actionbarsherlock.com/
  14. ActionBar • Google released a backport library for the Action

    Bar within the support lib (~2013). • Welcome ActionBarActivity! • Goodbye ActionBarSherlock 🥲 Y(HOLO) age Bye Sherlock! Hello Support! // Your theme android:theme="@style/Base.V7.Theme.AppCompat" // In your activity import android.support.v7.app.ActionBarActivity; public class RandomActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { getSupportActionBar().setTitle("Title"); getSupportActionBar().setBackgroundDrawable(...); setSupportProgressBarIndeterminateVisibility(true); setSupportProgressBarVisibility(true); } }
  15. ActionBar • With the rise of Material design we now

    have a concept of Toolbar • Component available since API 21. • A bit more flexible than the old ActionBar Material age From ActionBar to Toolbar class MyActivity : AppCompatActivity() <application android:theme="@style/Theme.AppCompat.Light.NoActionBar"/> <!-- Or androidx.appcompat.widget.Toolbar --> <android.support.v7.widget.Toolbar android:id="@+id/my_toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" android:theme="@style/ThemeOverlay.AppCompat.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_my) setSupportActionBar(findViewById(R.id.my_toolbar)) // Or use directly the Toolbar view }
  16. ActionBar • Same concepts but different name in Compose •

    High flexibility and customization • Part of the Compose Material library TopAppBar Composable K-age TopAppBar( title = { Text( text = getString(R.string.app_name), color = AppTheme.colors.textColors.prominentText, style = MaterialTheme.typography.h4.bold() ) }, backgroundColor = AppTheme.colors.backgroundColors.backgroundContent, actions = { IconButton( onClick = { // Do something } ) { Icon( imageVector = Icons.Filled.Settings, contentDescription = "Dark mode", tint = AppTheme.colors.icons.iconDefault, ) } } )
  17. Toolbar Composable give us lots of possibilities ActionBar might show

    some limitations Has many advantages Action Bar is a good UI/UX pattern Go 😎 Think! 🤔 Explore 🤓 ActionBar concepts did not evolve a lot since Material design intro ActionBar Build your own Action Bar
  18. 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 🚀
  19. 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
  20. 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>
  21. 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>
  22. 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
  23. 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
  24. 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); } }
  25. 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()); } }
  26. 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 }
  27. 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
  28. 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
  29. 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 }
  30. 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!") ) }
  31. Build native UI Compose is still “new” Move away from

    Kotlin 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
  32. Network calls • Directly use the Android stack: HttpURLConnection •

    Available since API 1 • Lots of boilerplates for requests! • Do not do that at home 😆 Y(HOLO) age HttpURLConnection try { URL urlObj = new URL(url); // Opens connection HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection(); conn.setDoOutput(true); // Sends data OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream()); out.write(params); out.flush(); // Read response data BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); InputSource is = new InputSource(in); // Creating XML DOM object try { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); builderFactory.setNamespaceAware(true); DocumentBuilder builder = builderFactory.newDocumentBuilder(); xmlDoc = builder.parse(is); } catch (Exception e) { throw new RuntimeException("An error occured"); } finally { out.close(); in.close(); } return xmlDoc; } catch (IOException e) { throw new RuntimeException("An error occured"); }
  33. Network calls • Build by Google since 2012 and still

    maintained! • REST calls, cancel requests, custom parser… • Still a lot of boilerplates to execute requests 😕 Y(HOLO) age Volley Material age K-age ?? https://github.com/google/volley // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this); String url = "https://www.myapi.io"; // Request a string response from the provided URL. // Can be a JsonObjectRequest to deal with JSON formats. StringRequest stringRequest = new StringRequest( Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { // Display the first 500 char of the response string. } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // Display an error } } ); // Add the request to the RequestQueue. queue.add(stringRequest);
  34. Network calls • Made and maintained by Square since 2013

    • REST calls, cancel requests, custom parser, interoperability with Kotlin… • Great implementation, easy to setup 🚀 Y(HOLO) age Retrofit Material age K-age https://square.github.io/retrofit/ // Define the routes interface UserWebService { @GET("users/{user}/repos") suspend fun listRepos( @Path("user") user: String ): Response<List<Repo>> } // Build the retrofit instance and add confirguration val retrofit = Retrofit.Builder() .baseUrl(endPoint) .addConverterFactory(GsonConverterFactory.create()) .client(OKclient()) .build() val service = retrofit.create(UserWebService::class.java) // Launch request scope.launch { val response = service.listRepos("myuser") if (response.isSuccessful) { // Do something } else { // Do something else 🥸 } }
  35. Network calls • Made and maintained by JetBrains • REST

    calls, great config, fully in Kotlin, multiplatform… • Functional approach, Great for Multiplatform! Ktor K-age https://ktor.io/docs/create-client.html private val ktorHttpClient = HttpClient(Android) { install(JsonFeature) { serializer = KotlinxSerializer(Json { prettyPrint = true isLenient = true ignoreUnknownKeys = true }) } install(Logging) { ... } install(DefaultRequest) { ... } } // GET Request client.get("https://myapi.io/users/123") // POST Request client.post<UserEntity>("https://myapi.io/users") { body = user }
  36. Explore Kotlin Ktor Stop using directly HttpURLConnection Maybe do not

    parse responses yourself Using Retrofit or Ktor Check GraphQL or gRPC Go 😎 Explore 🤓 Think! 🤔 Network calls
  37. 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
  38. 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…
  39. 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
  40. 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
  41. Architecture • Model-View-Intent • Separation of concerns, observability, state machine…

    • Great when using reactive programming and Unidirectional Data Flow (UDF) MVI Inspired by Adam McNeilly schema Model (Reducer) View Intent Activity, Fragment, Composable… Repository, DataSource, Reducer Material age K-age UI events Interaction Interpret/Reduce actions Dispatch new UI model to the View D i s p a t c h e r S t a t e
  42. 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
  43. Architecture • Well Clean-ish Architecture • Separation of concerns, abstraction,

    testable code • ❗Do not follow these concepts by the book: take inspiration and what’s working for your app! Clean Architecture https://antonioleiva.com/clean-architecture-android/ Material age K-age
  44. Architecture • Model-View-What You Want • Separation of concerns, maintainable

    and testable • Do not put business logic in View components MVWYW Y(HOLO) age Material age K-age
  45. 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);
  46. 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();
  47. 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
  48. 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 }
  49. 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 } });
  50. 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
  51. 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
  52. 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 */ } }
  53. 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! 🤔
  54. 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?
  55. 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))
  56. 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 } } );
  57. 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 🙈)
  58. 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. } });
  59. 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 } } }
  60. 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
  61. Scrolling content • Compose introduced LazyColum, LazyRow and LazyVerticalGrid. •

    No more XML, no more Adapter… Wesa freeee! • Great 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(), ) } }
  62. 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! 🤔
  63. 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
  64. 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/>
  65. 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/>
  66. 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/>
  67. 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! }
  68. 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); } }
  69. 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 ) } }
  70. 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
  71. 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 API migration might not be easy Go 😎 Explore 🤓 Permissions Think! 🤔
  72. Eclipse + ADT • First IDE and plugin to develop

    Android apps • (very) Slow emulator • Back then: no gradle, no dep management, no live edition, (almost) no tooling… • It was… an experience of life 😅 Y(HOLO) age
  73. Eclipse + ADT tooling • Limited tooling for dev back

    then! • Project creation wizard • Android Virtual Devices (AVD) to create and manage emulators • Basic debugging/tracing support • ADB, NDK support, layout editor… Y(HOLO) age
  74. IntelliJ • Starting 2011, it was possible to use IntelliJ

    IDEA for Android dev. • Same feature but in a shiner env. • 2012: improvements to debugging and profiling • 2013: new build tool system… Gradle 🔥 Y(HOLO) age Material age
  75. Android Studio • Android x IntelliJ = Android Studio 💚

    • First preview in 2013: Gradle build tools, Lint, build variant, layout editor… • Stable release in 2014: build improvements, memory profiler, code analysis… Y(HOLO) age Material age K-age Meanwhile in the Eclipse world…
  76. End of an era… • Novembre 2016: end of Eclipse

    + ADT 🥲 • I WAS nice but time to say goodbye • Future is bright with IntelliJ and Android Studio Material age
  77. Android Studio • And since 2016… we got great tools!

    • Instant run, Kotlin support, profiler, performance analysis, database inspection, Jetpack Compose live preview, new wizards… • Not always perfect but the tooling env. is rich and very useful! Material age K-age
  78. Gradle is wonderful and hateful at the same time No

    more Eclipse + ADT 🙏 Android Studio is gold mine Great tooling comes with great responsibilities Not easy to build your own Intellij/AS plugin Go 😎 Explore 🤓 Development env. Think! 🤔 Gradle Kotlin Script 💚
  79. 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