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

Architecture Android et bonnes pratiques

Architecture Android et bonnes pratiques

[DevoxxFr 2016:https://www.youtube.com/watch?v=yXWWuauVVEE]
Depuis maintenant 7 ans que je développe sous Android, ma principale préoccupation a toujours été l'architecture.

Et si nous prenions quelques heures pour en discuter ?
Je vous propose une vision globale et synthétique s'appuyant sur des exemples concrets, sur les principes et conseils de référence des équipes Google et sur des modèles d'architecture (MVP/n-tiers) et leur mises en place.
Au programme:
Le contexte Android,
L'objectif utilisateur,
La responsabilité du développeur,
Les bonnes pratiques (celles de Chet Haase, Romain Guy et les miennes),
Les principes d'architecture (n-tiers, MVP et MVVM),
Leur application sur Android (services, threads, Application ...),
Le déploiement continue,
Les librairies incontournables du moment,
Un exemple concret d'utilisation d'un service REST (up et download)
et bien sûr un projet github est associé à cette conférence pour que le code soit avec vous !

Tweet

More Decks by Mathias Seguy - Android2EE

Other Decks in Programming

Transcript

  1. Android2EE est référencé en tant qu’organisme de formation, vous pouvez

    faire prendre en charge tout ou partie du montant de cette formation par votre OPCA. Cette formation Initiation avancée à Android est éligible au titre du DIF et CIF. Lieu : Paris, Toulouse, Lyon Durée : 5 jours Prix : 2980 € 2
  2. Lieu : Paris Date : 10 - 13 Mai 2016

    Durée : 4 jours 3 Lieu : Lyon Date : 23 - 27 Mai 2016 Durée : 5 jours Lieu : Paris Date : 06 - 10 Juin 2016 Durée : 5 jours
  3. Le contexte est tout Papa, on peut atteindre Mach1 ?

    La réponse dépend du contexte ! DataCenter Desktop Tablette Smartphone You're here Light1, facile. Mach1, facile. Euh,... Qui me parle ? Allégorie de la puissance
  4. Le contexte est tout Papa, tu me sers à boire

    de l'eau, s'il te plait ? Water water.drink() giveWater() water.drink() null Le contexte Le contexte NPE
  5. La mémoire c'est la vie ! La mémoire Mais Vous

    développez avec les meilleurs devices de la planète !!! Performance Base de données locale et fichiers Persistance multi-processus
  6. La mémoire dans la vrai vie de vos utilisateurs !

    La mémoire HelloWorld.apk == 1 Mo faibles perfs pas d'espace Appareil Standard : 8Go (4GO courant)
  7. Dans la vrai vie Chaque utilisateur possède un CPU différent

    (tout pourri) Dans tes rêves Le CPU et GPU
  8. L'énergie Les causes du gaspillage énergétique Mauvaises pratiques de code

    Putain de javaïstes (Android c'est pas du Java sur des serveurs clusterisés !!!)
  9. Le contexte mutualiste sous Android La vie en collectivité gmail

    twitter hangout evernote messsager facebook gmap linkedIn runstatic your app chrome
  10. C'est le vivre tous ensemble qui est compliqué (pensez, aussi,

    aux sanitaires du camping municipal de cet été) La tragédie des biens communs
  11. Lui bosse, mais toi, tu bouges plus, t'es en mode

    freeze. GarbageCollector 10-20 ms avec Dalvik 2-3ms avec ART (mais il n'est pas ton ennemi)
  12. Dans un monde complexe Synthèse Mémoire Puissance Energie et de

    responsabilité collective Un contexte de pénuries 60fps UI Thread intouchable GC sans espace énergivore
  13. Et grâce à vous, il possède des capacités qu'il n'avait

    pas. ça me donne des super pouvoirs!
  14. Je veux faire un truc, je peux le faire, et

    quand je le fais, ça marche ! Je sais ce que je fais. C'est facile et c'est fluide. Ca me donne des super pouvoirs, sans authentification, ni paiement. Le plaisir! Combler un besoin utilisateur avec efficacité. L'utilisateur doit toujours savoir ce qu'il fait, où il est. Et quand il le fait, c'est facile, simple et bluffant. Et grâce à vous, il possède des capacités qu'il n'avait pas. Ne frustrer pas votre utilisateur, laissez le libre. Utilisateur Développeur
  15. sans télécharger pendant des heures des données, instancier quarante classes

    ou faire des tonnes de requêtes en base! Ca démarre instantanément
  16. ça va pas me lâcher au moment où j'en ai

    besoin! Et je peux avoir confiance Spéciale dédicace à la RATP ! Again !
  17. A la batterie (à ce qu'il en reste surtout). Adaptation

    <receiver android:name=".BatteryLevelReceiver"> <intent-filter> <action android:name="android.intent.action.ACTION_BATTERY_LOW"/> <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/> </intent-filter> </receiver>
  18. Aux versions Adaptation Android 4.1 JellyBean 27 Juin 2012 Android

    4.4 KitKat 3 Septembre 2013 Android 2.3 Gingerbread 06 Décembre 2010 Android 3.0 HoneyComb 10 Mai 2011 Android 4.0 IceCreamSandwich 19 Novembre 2011 Android 5.0 Lollipop 3 Novembre 2014 Android 6.0 MarshMallow 5 Octobre 2015
  19. 74 Pour réaliser mon objectif utilisateur Pour assumer mes responsabilités

    de développeur Pour m'adapter aux contraintes du système Pourquoi des bonnes pratiques ?
  20. N'allouez pas d'objets. Mémoire N'allouez pas d'objets dans les boucles

    et les méthodes appelées souvent (onDraw). Mettez les objets en cache (pour éviter de les réallouer) Utiliser des pools d'objets au besoin Utiliser des variables de classes temporaires (plutôt que des variables de méthodes) Utiliser ArrayMap/SimpleArray Map/SparseArray au lieux de HashMap Définissez la taille de vos ArrayList
  21. N'allouez pas d'objets. Mémoire Pas d'itérateurs, il faut for(int i=0;

    i<malist.size(); i++) Oubliez que les enums existent Que des librairies spécifiquement écrites pour Android public static Objet obj; peut générer des fuites mémoires Instanciez les objets à la demande LazyLoading Préférez les paramètres IO à vos méthodes que la création d'un objet
  22. N'allouez pas d'objets. Mémoire Répondez présent au CallBack onTrimMemory et

    désallouez Tuez vos Services dès que possible LargeHeap ? non c'est pas pour toi
  23. Interface, Factory et implémentations: La programmation par contrat. Adaptation if(postHoneyComb)

    if(postJellyBean) if(postKitKat) Factory Implémentation Interface Client res\values\versions.xml <resources> <bool name="postHoneyComb">false</bool> <bool name="postJellyBean">false</bool> <bool name="postKitKat">false</bool> </resources> res\values-v11\versions.xml <resources> <bool name="postHoneyComb">true</bool> <bool name="postJellyBean">false</bool> <bool name="postKitKat">false</bool> </resources> res\values-v16\versions.xml <resources> <bool name="postHoneyComb">true</bool> <bool name="postJellyBean">true</bool> <bool name="postKitKat">false</bool> </resources> connait Demande renvoie l'implémentation adaptée au contexte
  24. Interface, Factory et implémentations: La programmation par contrat. Adaptation Factory

    Interface Client if(postHoneyComb) if(postJellyBean) if(postKitKat) Versions if(wifi) if(3G) if(noconnectivity) connectivité if(ginger) if(ics) if(lollipop) Animations
  25. Interface, Factory et implémentations: La programmation par contrat. Adaptation Factory

    Interface Client if(batteryLow) if(batteryOk) if(charging) Batterie if(lowRam) if(casualRam) Faible appareil if(nexus) if(htc) if(samsung) Constructeur
  26. A la compilation: Gradle et Saveur (Flavor) Adaptation Gradle Code

    prod screendensity processeur versions staging/test Saveur
  27. UI Performance Pas de lecture/écriture disque Pas d'appel réseau ni

    de communication en général Pas de gros calcul pas de traitement pas de complexité
  28. Les opérations UI Thread coûteuses. UI Performance Mesurer et Disposer.

    Measure and Layout Inflation. Initialisation de l'objet Application.
  29. Les opérations UI Thread coûteuses. UI Performance Mesurer et Disposer.

    Inflation. Initialisation de l'objet Application. Complexité de la hiérarchie des vues. RelativeLayout.
  30. Pas d'animation durant les opérations coûteuses. UI Performance Les CustomViews

    permettent de simplifier le measure et layout. Démarrage Rapide : Utilisez la Starting Window avec le bon thème. Le ViewStub est ton ami. Le LinearLayout est ton meilleur ami. Choisissez bien vos Layouts en fonction de votre besoin. Simplifiez vos IHM, aplatissez vos layouts.
  31. !--Style for TextView--> <!-- ************************--> <style name=“mainTxv“ parent="@android:style/TextAppearance"> <item name=“android:textSize“>12sp</item>

    <item name=“android:textStyle“>bold</item> <item name=“android:background“>"@color/background"</item> <item name=“android:paddingLeft“>3dip</item> <item name=“android:paddingRight“>3dip</item> <item name=“android:textColor“> "@color/foreground" </item> <item name=“android:layout_width“>wrap_content</item> <item name=“android:layout_height“>wrap_content</item> </style> <style name=“mainEdt“ parent="mainTxv"> <item name=“android:textSize“>12sp</item> <item name=“android:textStyle“>bold</item> </style> 91 MyBaseTheme Theme Theme.Holo MyTheme ColorChart RefColors RefDimensions Style TxvStyle TxvTitleStyle EdtStyle BtnStyle And so on TxvWarningStyle TxvErrorStyle BtnTitleStyle BtnWarningStyle <TextView style=“@style/ TxvStyle“ android:text=“@string/hello“ /> UI Pro Tips Les styles sont tes meilleurs amis.
  32. UI Pro Tips Globalement cohérent: affectez votre style aux composants

    natifs. Custom Toast et Custom AlertDialog <?xml version="1.0" encoding="utf-8"?> <resources> <style name="Theme.Light.JCertif" parent="android:style/Theme.Holo.Light"> <item name="android:dropDownItemStyle">@style/Widget.DropDownItemLight</item> </style> <style name="Theme.Light.JCertif.preHC" parent="android:style/Theme.NoTitleBar"> <item name="android:dropDownItemStyle">@style/Widget.DropDownItemLight</item> </style> <style name="Widget.DropDownItemLight" parent="@android:style/Widget.DropDownItem"> <item name="android:textColor">@color/deep_blue</item> <item name="android:textSize">@dimen/textSize</item> <item name="android:textStyle">italic</item> </style> </resources> http://romannurik.github.io/AndroidAssetStudio/ http://blog.stylingandroid.com/archives/1267 https://github.com/android/platform_frameworks_base/blob/master/core/res/res/values/themes.xml https://github.com/android/platform_frameworks_base/blob/master/core/res/res/values/styles.xml http://www.androidviews.net/category/dev-tools/ http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html private void myMakeText() { LayoutInflater inflater = getLayoutInflater(); View layout = getLayoutInflater().inflate(R.layout.toast_layout, ); Toast toast = new Toast(this); toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0); toast.setDuration(Toast.LENGTH_LONG); toast.setView(layout); toast.show(); }
  33. Redirigez vos layouts Gravez dans le marbre le nombre de

    fragments affichés 93 •res\layout activity_a_two_panes_layout.xml (le vrai layout) activity_a_one_panes_layout.xml (le vrai layout) Dans ActivityA setContentView(R.layout.main_activity) • res\values  layout_redirection.xml <resources> <item name="main_activity" type="layout">@layout/activity_one_panes</item> <bool name="twoPane">false</bool> </resources> •res\values-land || res\values-large || res\layout-xlarge layout_redirection.xml <resources> <item name="main_activity" type="layout">@layout/activity_two_panes</item> <bool name="twoPane">true</bool> </resources> getRessource().getBoolean(R.bool.twoPane) UI Pro Tips
  34. Attention à l'overDraw. UI Pro Tips Utiliser la Starting Windows

    thèmée (vous pouvez mettre une image). Utiliser le Context approprié. Les vues ne doivent pas être connues des threads (autre que l'UI).
  35. Ajoutez votre connectivité (Wifi,..,edge) à vos requêtes Ajoutez la version

    des données à vos urls /myservlet_v1.2 MyOnlyObject in Json+GZip Ajoutez un TimeStamp à vos données dans vos bases de données (local & global) Requêtez vos données en vous appuyant sur les timestamp Utilisez json + gzip (ou mieux: des dataBuffer) C'est le serveur qui effectue le Prefetch des données (basé sur votre connectivité) TimeStamp TimeStamp /myservlet_v1.2?timespan=11215982 /myservlet_v1.2?timespan=11215982&net=wifi Utilisez la Big Cookies stratégie. Réseau
  36. Réseau Utiliser GCM NetworkManager (JobScheduler) Ne sur-synchroniser pas ! Vous

    n'êtes pas seul. Utilisez GCM. Vérifiez le réseau avant de faire un appel. N'oubliez pas les zones a faible débit (Afrique, Aveyron...).
  37. Tu n'es pas un Javaïste Parcelable c'est super.... pour les

    intents. Pas de stockage disque !!! Utilisez les structures Android : SparseArray, ArrayMap,... Oubliez que la sérialisation existe. Utilisez les SharedPref Préférez les types primitifs et attention à l'auto-boxing
  38. Persistance Jamais de chemin en dur, jamais, seulement du relatif.

    N'utilisez pas SQLite pour de la persistance simple Une base de données peut posséder plusieurs Tables...
  39. Objectif Interne Fichiers privés, protégés, propres à l'application. Détruits lors

    de la désinstallation. Cache Fichiers temporaires propres à l'application. Détruits lors de la désinstallation. External Fichiers privés et publics, non-protégés, propres à l'application. Détruits lors de la désinstallation. Public External Fichiers publics, non-protégés. Non détruits lors de la désinstallation. Persistance Pour les fichiers : Connaître son objectif.
  40. Méthode pour récupérer le dossier racine Interne File getFilesDir() Cache

    File getCacheDir() External File getExternalStorage(String type); Public External File getExternalStoragePublicDirectory(String type); Persistance Utilisez les classes File, FileInputStream et FileOutputStream. ou BufferSink et BufferSource... Quand on Connaît son objectif, tout devient facile.
  41. Persistance Vérifiez la présence du fichier avant d'écrire ou de

    lire... //Then read your file File filePicture = new File(subFolder, fileNameStr); if(filePicture.exist()){ //Then open an InputStream on it your file FileInputStream fis = new FileInputStream(filePicture);
  42. Persistance Vérifiez l'état de l'ExternalDirectory avant d'éssayer de lire/écrire. String

    externalStorageState = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) { // We can read and write the media} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState)) { // We can only read the media}
  43. Thread Centralisez leur gestion. La gestion des Threads est cruciale.

    Faîtes attention aux fuites mémoires. Utilisez un ServiceExecutor Utilisez PLUSIEURS ServiceExecutor MyApplication ServiceExecutor
  44. /** * The ThreadPool Executor for caching background */ ExecutorService

    executor = Executors.newFixedThreadPool(6, null); /** * Launch a Runnable*/ public synchronized void cacheBackGround() { executor.submit(mCacheBackgroundRunnnable); } Thread ExecutorService utilise un ThreadPoolExecutor
  45. if (executor != null) { executor.shutdown(); // Disable new tasks

    from being submitted try {//as long as your threads hasn't finished while (!executor.isTerminated()) { // Wait a while for existing tasks to terminate if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { // Cancel currently executing tasks executor.shutdownNow(); Log.e("MyApp","Probably a memory leak here"); } } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted executor.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); Log.e("MyApp","Probably a memory leak here too"); }}} Thread N'oubliez pas de les détruire
  46. Memory Leak Fuite mémoire courante : Un Thread pointant vers

    l'IHM Thread I.H.M Thread de traitement Activity Handler sendMessage() message Thread I.H.M Thread de traitement Activity sendMessage() message Handler OnDestroy() OnCreate() Thread de traitement initiale sendMessage() Activity fantôme Handler fantôme Utilisez WeakReference
  47. Fuite mémoire courante : Un Thread pointant vers l'IHM Accorder

    le cycle de vie de la thread avec celui de l'activité Thread I.H.M Thread de traitement Activity Handler sendMessage() Thread I.H.M Thread de traitement Activity sendMessage() message message OnDestroy() OnCreate() Handler Memory Leak
  48. Fuite mémoire courante : Un Thread pointant vers l'IHM Conserver

    la Thread Thread I.H.M Thread de traitement Activity Handler sendMessage() Thread I.H.M Activity message message Thread de traitement Handler sendMessage() OnDestroy() OnCreate() !!!Attention ne pas utiliser en l’état !!! !!! Fuite mémoire !!! Memory Leak
  49. Fuite mémoire courante : Non static inner class Utilisez les

    static inner class public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); exampleOne(); } private void exampleOne() { new Thread() { public void run() { while (true) {SystemClock.sleep(1000); } }}.start(); }} Non Static Inner Class public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyThread().start(); } private static class MyThread extends Thread { public void run() { while (true) {SystemClock.sleep(1000);} }} } Memory Leak
  50. Fuite mémoire courante : private static avec une référence implicite

    vers la classe englobante Ne pas le faire public class MainActivity extends Activity { private static Drawable sBackground; protected void onCreate(Bundle state) { super.onCreate(state); TextView label = new TextView(this); if (sBackground == null) { sBackground = getDrawable(R.drawable.largebitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }} Mem Leak Memory Leak
  51. public class Cst { public static int MaConstante=1; } public

    class DamnedService extends Service { private void somewhere(int val){ if(val==Cst.MaConstante){...} } } Fuite mémoire courante : public static dans un objet lourd Extraire les constantes dans une classe dédiée Mem Leak Memory Leak public class MainActivity extends Activity { public static int MaConstante=1; } public class DamnedService extends Service { private void somewhere(int val){ if(val==MainActivity.MaConstante){...} } }
  52. Architecture Activity est l'entrée principale de votre application. Don't fuck

    around with the system ! Un Service permet de dire au système "Je fais une longue opération, considère moi comme une Activité visible sans IHM." BroadcastReceiver indique au système qu'il est intéressé pour recevoir telle ou telle information.
  53. Un peu d'histoire public class HistoryBattleActivity extends AppCompatActivity { private

    static final String TAG = "HistoryBattleActivity"; private static final int CONTEXT_CURRENT=110274; private static final int CONTEXT_HISTORY=131274; /*********************************************************** * Attributes **********************************************************/ BattleFragment battleFragment; /** * Current context History/current */ int currentContext=CONTEXT_CURRENT; /*********************************************************** * Managing RoundTrip animation (VectorDrawable1 to VectorDrawable 2 and back again ********************************************************** /** * The LevelList that contains only two AnimatedVectorDrawable, * the ones used to go from on to the other */ LevelListDrawable backupRoundTrip; /** * The current AnimatedVector diaplsyed by the RoundTrip */ AnimatedVectorDrawable contextDrawable; /** * To know is the animation have been already launched */ boolean backupRoundTripFirstLaunched=true; /** * Historical battles */ ArrayList<Long> battlesId=null; /*********************************************************** * Attributes for the ViewPager **********************************************************/ /** * The TabLayout itself :) */ TabLayout tabLayout; /** * The page Adapter : Manage the list of views (in fact here, it's fragments) * And send them to the ViewPager */ private MyPagerAdapter pagerAdapter; /** * The ViewPager is a ViewGroup that manage the swipe from left to right to left * Like a listView with a gesture listener... */ private ViewPager viewPager; /*********************************************************** * Managing the Life cycle **********************************************************/ **********************************************************/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG, "onCreate() called"); setContentView(R.layout.activity_history); //find the Toolbar Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); //use it as your action bar setSupportActionBar(toolbar); getSupportActionBar().setSubtitle(getString(R.string.history_fragment_subtitle)); getSupportActionBar().setTitle(getString(R.string.history_fragment_title)); tabLayout = (TabLayout) findViewById(R.id.tabLayout); //Define its gravity and its mode tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE); //Define the color to use (depending on the state a different color should be disaplyed) //Works only if done before adding tabs tabLayout.setTabTextColors(getResources().getColorStateList(R.color.tab_selector_color)); //instanciate the PageAdapter pagerAdapter=new MyPagerAdapter(this,true); //Find the viewPager viewPager = (ViewPager) super.findViewById(R.id.viewpager); // Affectation de l'adapter au ViewPager viewPager.setAdapter(pagerAdapter); viewPager.setClipToPadding(true); //Add animation when the page are swiped //this instanciation only works with honeyComb and more //if you want it all version use AnimatorProxy of the nineoldAndroid lib //@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer //TODO uncomment those lines and the opengl bug disappears // if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){ // viewPager.setPageTransformer(true, new MyPageTransformer()); // } //AND CLUE TABLAYOUT AND VIEWPAGER tabLayout.setupWithViewPager(viewPager); } @Override protected void onStart() { super.onStart(); //track entrance Log.e(TAG, "onStart() has been called"); EventBus.getDefault().register(this); } @Override protected void onResume() { super.onResume(); //track entrance Log.e(TAG, "onResume() has been called"); } } @Override protected void onStop() { super.onStop(); //track entrance Log.e(TAG, "onStop() has been called"); EventBus.getDefault().unregister(this); } @Override public void onBackPressed() { if(((MyApplication)getApplication()).isCigaretPanelOpen){ //do nothing the fragment will just change its state battleFragment.onBack(); }else{ super.onBackPressed(); } } @Subscribe(threadMode = ThreadMode.MAIN)//EventBus public void updateUI(FullUpdateEvent event) { //TODO update properly Log.e(TAG, "fullUpdate() called with: " + "notUsed = [" + event + "]"); //rebuild every thing:$ pagerAdapter.notifyRebuildAll(); if(event.isSwitchActivity()){ switchContext(); }else{ tabLayout.setupWithViewPager(viewPager); } } /*********************************************************** * Managing menu **********************************************************/ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater=getMenuInflater(); inflater.inflate(R.menu.history_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.menu_switch_context: switchContext(); break; } return super.onOptionsItemSelected(item); } /*********************************************************** * Managing backup button round trip **********************************************************/ /** * Switch context from history to current (and vis versa) * Launch the animation on the currentAnimatedVectorDrawable */ private void switchContext(){ Intent startNewContext=new Intent(this, CurrentBattleActivity.class); startNewContext.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(startNewContext); } } Au début, il y avait la classe dieu (God Class) On a une petite évol ! On va tester l'évol... Bref, on avait fait de la m***e
  54. Un peu d'histoire public class HistoryBattleActivity extends AppCompatActivity { private

    static final String TAG = "HistoryBattleActivity"; private static final int CONTEXT_CURRENT=110274; private static final int CONTEXT_HISTORY=131274; /*********************************************************** * Attributes **********************************************************/ BattleFragment battleFragment; /** * Current context History/current */ int currentContext=CONTEXT_CURRENT; /*********************************************************** * Managing RoundTrip animation (VectorDrawable1 to VectorDrawable 2 and back again ********************************************************** /** * The LevelList that contains only two AnimatedVectorDrawable, * the ones used to go from on to the other */ LevelListDrawable backupRoundTrip; /** * The current AnimatedVector diaplsyed by the RoundTrip */ AnimatedVectorDrawable contextDrawable; /** * To know is the animation have been already launched */ boolean backupRoundTripFirstLaunched=true; /** * Historical battles */ ArrayList<Long> battlesId=null; /*********************************************************** * Attributes for the ViewPager **********************************************************/ /** * The TabLayout itself :) */ TabLayout tabLayout; /** * The page Adapter : Manage the list of views (in fact here, it's fragments) * And send them to the ViewPager */ private MyPagerAdapter pagerAdapter; /** * The ViewPager is a ViewGroup that manage the swipe from left to right to left * Like a listView with a gesture listener... */ private ViewPager viewPager; /*********************************************************** * Managing the Life cycle **********************************************************/ **********************************************************/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG, "onCreate() called"); setContentView(R.layout.activity_history); //find the Toolbar Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); //use it as your action bar setSupportActionBar(toolbar); getSupportActionBar().setSubtitle(getString(R.string.history_fragment_subtitle)); getSupportActionBar().setTitle(getString(R.string.history_fragment_title)); tabLayout = (TabLayout) findViewById(R.id.tabLayout); //Define its gravity and its mode tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE); //Define the color to use (depending on the state a different color should be disaplyed) //Works only if done before adding tabs tabLayout.setTabTextColors(getResources().getColorStateList(R.color.tab_selector_color)); //instanciate the PageAdapter pagerAdapter=new MyPagerAdapter(this,true); //Find the viewPager viewPager = (ViewPager) super.findViewById(R.id.viewpager); // Affectation de l'adapter au ViewPager viewPager.setAdapter(pagerAdapter); viewPager.setClipToPadding(true); //Add animation when the page are swiped //this instanciation only works with honeyComb and more //if you want it all version use AnimatorProxy of the nineoldAndroid lib //@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer //TODO uncomment those lines and the opengl bug disappears // if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){ // viewPager.setPageTransformer(true, new MyPageTransformer()); // } //AND CLUE TABLAYOUT AND VIEWPAGER tabLayout.setupWithViewPager(viewPager); } @Override protected void onStart() { super.onStart(); //track entrance Log.e(TAG, "onStart() has been called"); EventBus.getDefault().register(this); } @Override protected void onResume() { super.onResume(); //track entrance Log.e(TAG, "onResume() has been called"); } } @Override protected void onStop() { super.onStop(); //track entrance Log.e(TAG, "onStop() has been called"); EventBus.getDefault().unregister(this); } @Override public void onBackPressed() { if(((MyApplication)getApplication()).isCigaretPanelOpen){ //do nothing the fragment will just change its state battleFragment.onBack(); }else{ super.onBackPressed(); } } @Subscribe(threadMode = ThreadMode.MAIN)//EventBus public void updateUI(FullUpdateEvent event) { //TODO update properly Log.e(TAG, "fullUpdate() called with: " + "notUsed = [" + event + "]"); //rebuild every thing:$ pagerAdapter.notifyRebuildAll(); if(event.isSwitchActivity()){ switchContext(); }else{ tabLayout.setupWithViewPager(viewPager); } } /*********************************************************** * Managing menu **********************************************************/ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater=getMenuInflater(); inflater.inflate(R.menu.history_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.menu_switch_context: switchContext(); break; } return super.onOptionsItemSelected(item); } /*********************************************************** * Managing backup button round trip **********************************************************/ /** * Switch context from history to current (and vis versa) * Launch the animation on the currentAnimatedVectorDrawable */ private void switchContext(){ Intent startNewContext=new Intent(this, CurrentBattleActivity.class); startNewContext.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(startNewContext); } } On va découpler la vue du reste de l'application
  55. Un peu d'histoire public class HistoryBattleActivity extends AppCompatActivity { private

    static final String TAG = "HistoryBattleActivity"; private static final int CONTEXT_CURRENT=110274; private static final int CONTEXT_HISTORY=131274; /*********************************************************** * Attributes **********************************************************/ BattleFragment battleFragment; /** * Current context History/current */ int currentContext=CONTEXT_CURRENT; /*********************************************************** * Managing RoundTrip animation (VectorDrawable1 to VectorDrawable 2 and back again ********************************************************** /** * The LevelList that contains only two AnimatedVectorDrawable, * the ones used to go from on to the other */ LevelListDrawable backupRoundTrip; /** * The current AnimatedVector diaplsyed by the RoundTrip */ AnimatedVectorDrawable contextDrawable; /** * To know is the animation have been already launched */ boolean backupRoundTripFirstLaunched=true; /** * Historical battles */ ArrayList<Long> battlesId=null; /*********************************************************** * Attributes for the ViewPager **********************************************************/ /** * The TabLayout itself :) */ TabLayout tabLayout; /** * The page Adapter : Manage the list of views (in fact here, it's fragments) * And send them to the ViewPager */ private MyPagerAdapter pagerAdapter; /** * The ViewPager is a ViewGroup that manage the swipe from left to right to left * Like a listView with a gesture listener... */ private ViewPager viewPager; /*********************************************************** * Managing the Life cycle **********************************************************/ **********************************************************/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e(TAG, "onCreate() called"); setContentView(R.layout.activity_history); //find the Toolbar Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); //use it as your action bar setSupportActionBar(toolbar); getSupportActionBar().setSubtitle(getString(R.string.history_fragment_subtitle)); getSupportActionBar().setTitle(getString(R.string.history_fragment_title)); tabLayout = (TabLayout) findViewById(R.id.tabLayout); //Define its gravity and its mode tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE); //Define the color to use (depending on the state a different color should be disaplyed) //Works only if done before adding tabs tabLayout.setTabTextColors(getResources().getColorStateList(R.color.tab_selector_color)); //instanciate the PageAdapter pagerAdapter=new MyPagerAdapter(this,true); //Find the viewPager viewPager = (ViewPager) super.findViewById(R.id.viewpager); // Affectation de l'adapter au ViewPager viewPager.setAdapter(pagerAdapter); viewPager.setClipToPadding(true); //Add animation when the page are swiped //this instanciation only works with honeyComb and more //if you want it all version use AnimatorProxy of the nineoldAndroid lib //@see:http://stackoverflow.com/questions/15767729/backwards-compatible-pagetransformer //TODO uncomment those lines and the opengl bug disappears // if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){ // viewPager.setPageTransformer(true, new MyPageTransformer()); // } //AND CLUE TABLAYOUT AND VIEWPAGER tabLayout.setupWithViewPager(viewPager); } @Override protected void onStart() { super.onStart(); //track entrance Log.e(TAG, "onStart() has been called"); EventBus.getDefault().register(this); } @Override protected void onResume() { super.onResume(); //track entrance Log.e(TAG, "onResume() has been called"); } } @Override protected void onStop() { super.onStop(); //track entrance Log.e(TAG, "onStop() has been called"); EventBus.getDefault().unregister(this); } @Override public void onBackPressed() { if(((MyApplication)getApplication()).isCigaretPanelOpen){ //do nothing the fragment will just change its state battleFragment.onBack(); }else{ super.onBackPressed(); } } @Subscribe(threadMode = ThreadMode.MAIN)//EventBus public void updateUI(FullUpdateEvent event) { //TODO update properly Log.e(TAG, "fullUpdate() called with: " + "notUsed = [" + event + "]"); //rebuild every thing:$ pagerAdapter.notifyRebuildAll(); if(event.isSwitchActivity()){ switchContext(); }else{ tabLayout.setupWithViewPager(viewPager); } } /*********************************************************** * Managing menu **********************************************************/ @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater=getMenuInflater(); inflater.inflate(R.menu.history_menu, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.menu_switch_context: switchContext(); break; } return super.onOptionsItemSelected(item); } /*********************************************************** * Managing backup button round trip **********************************************************/ /** * Switch context from history to current (and vis versa) * Launch the animation on the currentAnimatedVectorDrawable */ private void switchContext(){ Intent startNewContext=new Intent(this, CurrentBattleActivity.class); startNewContext.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(startNewContext); } } On va découpler la vue du reste de l'application Model Vue Controlleur Modifie Préviens Mets à jour Pour fêter ça, on a dansé la danse de la victoire Le modèle MVC était né!
  56. Un peu d'histoire puis vint le modèle M(VC) swing Model

    Vue&Controller Modifie Mets à jour Vue Controlleur
  57. Gère les données affichées Affiche les données interagit avec l'utilisateur

    Un peu d'histoire le modèle M(VC) swing Model Vue&Controller 1 1
  58. Un peu d'histoire le modèle M(VC) swing Gère les données

    affichées Model Affiche les données interagit avec l'utilisateur Vue&Controller 1 1
  59. Un peu d'histoire Apparition du MVP Gère les données affichées

    Model Affiche les données interagit avec l'utilisateur Vue&Controller Presenter La logique Business Et les données Model View
  60. Un peu d'histoire Apparition du MVP Gère les données affichées

    Presenter Affiche les données interagit avec l'utilisateur View La logique Business Et les données Model 1 1 Modifie Mets à jour Dialogue
  61. Un peu d'histoire Apparition de la programmation par contrat (Interfaces)

    Gère les données affichées Presenter Affiche les données interagit avec l'utilisateur View La logique Business Et les données Model 1 0 PresenterIntf ViewIntf 0 1
  62. Un peu d'histoire Pour les tests !!! Model Presenter View

    PresenterIntf ViewIntf MockPresenter MockView
  63. Saveur=TestPresenter Un peu d'histoire Avec Gradle, c'est le bonheur des

    tests. Saveur=TestVue Saveur=PROD Model Presenter View 0 1 MockPresenter MockView View Presenter 0 1 0 1 MockModel
  64. Un peu d'histoire Apparition du MVVM Gère les données affichées

    Presenter Affiche les données interagit avec l'utilisateur View La logique Business Et les données Model 1 1 Modifie Mets à jour Dialogue ViewModel 1 n
  65. Architecture Mais pourquoi tout ça ? Tester ! Séparer les

    responsabilités Réutiliser Simplifier
  66. Architecture But wait ! Première remarque. MVC MVP MVVM FLUX

    On s'en fout !!! C'est pas ça l'important !!! C'est pareil à trois poils de *** prêt !
  67. Architecture But wait ! Vous avez pas oublié un truc?

    Ce ne sont que des architectures de la couche VUE !!!
  68. Modèle n-tier Application Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows starts starts knows View = MVP Transverse I N T F Service I N T F Communication Com D.A.O. I N T F DAO knows
  69. Attention Mauvaise nomenclature La malédiction Services AndroidServices SingletonServices I N

    T F Service AndroidServices Service Métier Business Traitement Logique
  70. Séparation Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  71. Simplicité Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  72. Responsabilités Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  73. Contrat Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  74. Réutilisabilité Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  75. Modularité Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  76. Evolution Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver ExceptionManager

    POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application I N T F I N T F I N T F
  77. Modèle n-tier MockView View = MVP Presenter ViewIntf 1 1

    JUNIT Services AndroidServices SingletonServices Service I N T F Tests Tester
  78. Modèle n-tier View = MVP Presenter View 1 1 Services

    AndroidServices SingletonServices Service I N T F Espresso AndroidTest SDK Tests Tester
  79. Testabilité complète Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver

    ExceptionManager POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application
  80. Maintenabilité Robustesse Modèle n-tier Services View Presenter AndroidServices SingletonServices BroadcastReceiver

    ExceptionManager POJO Tools knows knows knows starts starts knows View = MVP Service Communication Com D.A.O. DAO Transverse Application
  81. Modèle n-tier Service Vues COM DAO Transverse Use case 1

    Use case 2 Use case 3 Architecture en couches Architecture en sillots On s'en fout !!! On fait les deux. Naturellement.
  82. Modèle n-tier Architecture en couches Architecture en sillots Service Vues

    COM DAO Transverse Use case 1 Use case 2 Use case 3
  83. Modèle n-tier Architecture de la vraie vie Service Vues COM

    DAO Transverse Use case 1 Use case 2 Use case 3 On commence par un use case et après on réutilise dès qu'on peut
  84. Vues 164 Rangez votre package view ! Un sous package

    par fragment Un sous package par composant graphique complexe de l'activité Au root du package, l'activité et son presenter Un package par vue
  85. Limiter les dépendances 165 Couche IHM HumanViewParentIntf HumanPresParentIntf FamilyView FamilyPresenter

    1 1 HumanView HumanPresenter 1 1 use use Vues Vue contenue et réutilisable Utilisez des interfaces Couche IHM HumanViewParentIntf HumanPresParentIntf FamilyActivity FamilyPresenter 1 1 HumanFragment HumanPresenter 1 1 use use
  86. MVP + N-Tier mettre en place le principe de séparation

    des responsabilités Etre testable unitairement simplement permettre de s'adapter aux changements du framework Synthèse Sans impact, bref d'être indépendant de l'implémentation et de pouvoir la modifier et la tester permettre de faire évoluer les UI, la BD,... permettre de changer de librairies