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 45min 😅)

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

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 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…) Y(HOLO) age Material age K-age

Slide 16

Slide 16 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 Development Env.

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

ActionBar Elementary my dear Watson!

Slide 20

Slide 20 text

Nothing… No ActionBar concept

Slide 21

Slide 21 text

ActionBar ● At the beginning, we had the AppBar. ● Displaying app or Activity name. ● Not some many things to customize Y(HOLO) age

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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: ... } }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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!

Slide 26

Slide 26 text

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); } }

Slide 27

Slide 27 text

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/

Slide 28

Slide 28 text

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); } }

Slide 29

Slide 29 text

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() 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 }

Slide 30

Slide 30 text

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, ) } } )

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Build native UI Here is my point of view…

Slide 33

Slide 33 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 34

Slide 34 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 35

Slide 35 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 36

Slide 36 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 37

Slide 37 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 38

Slide 38 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 39

Slide 39 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 40

Slide 40 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 41

Slide 41 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 42

Slide 42 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 43

Slide 43 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 44

Slide 44 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 45

Slide 45 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 46

Slide 46 text

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

Slide 47

Slide 47 text

Network calls Fetch me maybe!

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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() { @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);

Slide 50

Slide 50 text

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> } // 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 🥸 } }

Slide 51

Slide 51 text

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("https://myapi.io/users") { body = user }

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Architectures One does not simply…

Slide 54

Slide 54 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 55

Slide 55 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 56

Slide 56 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 57

Slide 57 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 58

Slide 58 text

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

Slide 59

Slide 59 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 60

Slide 60 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 working for your app! Clean Architecture https://antonioleiva.com/clean-architecture-android/ Material age K-age

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

IT DEPENDS 🤷 Go 😎 Explore 🤓 Architecture Think! 🤔

Slide 63

Slide 63 text

Background work Full story in the thread 🧵

Slide 64

Slide 64 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 65

Slide 65 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 66

Slide 66 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 67

Slide 67 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 68

Slide 68 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 69

Slide 69 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 70

Slide 70 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 71

Slide 71 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 72

Slide 72 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 73

Slide 73 text

Scrolling content They see me scrolling…

Slide 74

Slide 74 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 75

Slide 75 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 76

Slide 76 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 77

Slide 77 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 78

Slide 78 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 79

Slide 79 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 80

Slide 80 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 81

Slide 81 text

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(), ) } }

Slide 82

Slide 82 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 83

Slide 83 text

Permissions Granted!

Slide 84

Slide 84 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 85

Slide 85 text

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

Slide 86

Slide 86 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 87

Slide 87 text

No content

Slide 88

Slide 88 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 89

Slide 89 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 90

Slide 90 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 91

Slide 91 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 92

Slide 92 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 93

Slide 93 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 API migration might not be easy Go 😎 Explore 🤓 Permissions Think! 🤔

Slide 94

Slide 94 text

Development Env. The moon will Eclipse the sun…

Slide 95

Slide 95 text

At the beginning…

Slide 96

Slide 96 text

No content

Slide 97

Slide 97 text

No content

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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…

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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 💚

Slide 106

Slide 106 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 107

Slide 107 text

Merci ! 😃 Julien Salvi - Android GDE @JulienSalvi Have fun with the droid!