Développer une app Android en 2015

Développer une app Android en 2015

Retour d'expérience chez BlaBlaCar - PAUG 3 mars 2015

Ea844bbe25d382c7eb96c711bed152d1?s=128

Alexandra Tritz

March 03, 2015
Tweet

Transcript

  1. Développer une app Android en 2015

  2. Saad Bouchehboun Tech Manager Francois-Xavier Oxeda Android developer @BlaBlaCarTech Alexandra

    Tritz Android developer
  3. Mobile First Jan. 2013 → 2015

  4. None
  5. None
  6. Librairies utilisées

  7. ButterKnife public class ForgotPasswordActivity extends Activity { @InjectView(R.id.new_password_header) TextView newPasswordHeader;

    @InjectView(R.id.new_password_editText) EditText newPasswordEditText; @InjectView(R.id.confirm_password_editText) EditText confirmPasswordEditText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_forgot_password); ButterKnife.inject(this); // Instead of // newPasswordHeader = (TextView) findViewById(R.id.new_password_header); // …
  8. ButterKnife @Optional @OnClick(R.id.drawer_logout_textView) public void logout() { // ... }

    @Optional @OnClick(R.id.drawer_login_textView) public void login() { // ... } @OnItemSelected(R.id.listView) public void onItemSelected(int position){ // ... }
  9. Universal Image Loader public void displayUser(User user) { ImageLoader.getInstance().displayImage( user.getPictureUrl(),

    userImageView ); // ... } public void displayUser(User user) { ImageLoader.getInstance().displayImage( user.getPictureUrl(), userImageView, options, listener ); // ... }
  10. Merge Adapter public class UpcomingRidesAdapter extends MergeAdapter { private TripOfferAdapter

    driverAdapter; private SeatBookedAdapter seatsBookedAdapter; private TripListAdapter visitedAdapter; public UpcomingRidesAdapter(Context context) { // driver trip offers sub-adapter driverAdapter = new TripOfferAdapter(context); addAdapter(driverAdapter); //booked seats seatsBookedAdapter = new SeatBookedAdapter(context); addAdapter(seatsBookedAdapter); // visited trips sub-adapter visitedAdapter = new TripListAdapter(context); addAdapter(visitedAdapter); } }
  11. Retrofit A type-safe REST client for Android and Java

  12. Otto public class BusManager { private static BusManager instance; public

    Bus bus; private BusManager() { bus = new Bus(); } public static BusManager getInstance() { if (instance == null) { instance = new BusManager(); } return instance; } }
  13. Otto // Publishing BusManager.getInstance().bus.post( new NotificationCountEvent(result) );

  14. Otto // Subscribing @Override public void onResume() { super.onResume(); BusManager.getInstance().bus.register(this);

    } @Override public void onPause() { super.onPause(); BusManager.getInstance().bus.unregister(this); } @Subscribe public void onNotificationCountEvent(NotificationCountEvent event) { // Update UI }
  15. Autres... Crouton ViewPagerIndicator

  16. Traductions Over The Air

  17. OpenLocalization

  18. openl10n.io

  19. Déploiement des traductions Les traductions sont synchronisés sur openl10n Une

    fois éditées elles sont directement déployées en prod (S3)
  20. Les traductions sont mises à jour dynamiquement à l’ ouverture

    de l’application
  21. strings-en.json { "contact.item.text.call_phone": "Call", "contact.item.text.text_phone": "Send a SMS", ... }

    strings-fr.json { "contact.item.text.call_phone": "Appeler", "contact.item.text.text_phone": "Envoyer un SMS", ... }
  22. ids_ext_strings.xml <item type="id" name="str_contact.item.text.call_phone"/> MainActivity.java BlablacarApplication.getExtString( this.getContext(), R.id.str_contact.item.text.call_phone); activity_main.xml <TextView

    typeandroid:text="@id/str_contact.item.text.call_phone"/>
  23. Configuration localisée

  24. None
  25. None
  26. GET api.blablacar.com/trips/1234 Accept-Language: fr-FR X-Currency: EUR HTTP vs Localization

  27. Values-fr/context.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="locale">fr_FR</string> <string name="cur">EUR</string> <bool

    name="show_date_filter_in_settings">false</bool> <bool name="show_using_highways_setting">true</bool> <bool name="is_booking">true</bool> <string name="tagmanager_container_id">GTM-XXXXXX</string> <bool name="can_select_currency">false</bool> </resources> getResources().getString(R.string.locale);
  28. Now

  29. Deeplink

  30. None
  31. blablacar://pwd

  32. mobiledeeplinking.org

  33. Schéma AndroidManifest.xml <activity android:name="org.mobiledeeplinking.android.MobileDeepLinking"> <intent-filter> <data android:scheme="blablacar" /> ...

  34. Routes MobileDeepLinkingConfig.json { "defaultRoute": { "class": "com.comuto.v3.activity.MainActivity" }, "routes": {

    "search": { "class": "com.comuto.v3.activity.SearchActivity" "...":
  35. Deeplink

  36. Librairies

  37. Exécute des requêtes asynchrones Respecte le cycle de vie des

    activités Cache les réponses
  38. RoboSpice private SpiceManager spiceManager = new SpiceManager(SpiceService.class); @Override protected void

    onStart() { super.onStart(); spiceManager.start(this); } @Override protected void onStop() { spiceManager.shouldStop(); super.onStop(); } spiceManager.execute(spiceRequest, requestListener);
  39. RoboSpice // Request public class MySpiceRequest extends SpiceRequest<T> { public

    MySpiceRequest(Class<T> clazz) { super(clazz); } @Override public T loadDataFromNetwork() throws Exception { // Background process } }
  40. RoboSpice // Listener public class MyRequestListener implements RequestListener<T> { @Override

    public void onRequestFailure(SpiceException spiceException) { } @Override public void onRequestSuccess(T t) { } }
  41. + Retrofit

  42. RoboSpice + Retrofit // Interface public interface RetrofitInterface { @GET("/public_session")

    Session getPublicSession(); }
  43. RoboSpice + Retrofit // RetrofitGsonSpiceService public class MyRetrofitSpiceService extends RetrofitGsonSpiceService

    { @Override public void onCreate() { super.onCreate(); addRetrofitInterface(RetrofitInterface.class); } @Override protected String getServerUrl() { return "http://www.base-url.com/"; } }
  44. RoboSpice + Retrofit // Interface: public class GetPublicSessionRequest extends RetrofitSpiceRequest<Session,

    RetrofitInterface> { public GetPublicSessionRequest(Class<Session> clazz, Class<RetrofitInterface> interfaceClass) { super(clazz, interfaceClass); } @Override public Session loadDataFromNetwork() throws Exception { return getService().getPublicSession(); } }
  45. RoboSpice + Retrofit // Callback Interfaces public interface ManagerErrorCallback {

    public void onFailed(BlablacarError error); public void onFailed(List<BlablacarFormError> error); public void onNoNetworkError(); } public interface ManagerCallback<T> extends ManagerErrorCallback { public void onSuccess(T result); }
  46. RoboSpice + Retrofit public class BaseActivity extends Activity implements ManagerErrorCallback

    { // ... } public class SomeActivity extends BaseActivity implements ManagerCallback<T> { // ... }
  47. RoboSpice + Retrofit public class BaseManager<T> implements RequestListener<T> { protected

    ManagerCallback<T> callback; protected SpiceManager spiceManager; protected BaseManager(SpiceManager spiceManager, ManagerCallback<T> callback) { this.spiceManager = spiceManager; this.callback = callback; } @Override public void onRequestFailure(SpiceException spiceException) { // Read exception and call the right callback accordingly // callback.onFailed(BlablacarError error); // callback.onFailed(List<BlablacarFormError> error); // callback.onNoNetworkError(); } @Override public void onRequestSuccess(T result) { callback.onSuccess(result); } protected void execute(RetrofitSpiceRequest request) { spiceManager.execute(request, this); } } BaseManager UserManager TripManager PaymentManager
  48. RoboSpice + Retrofit public class UserManager extends BaseManager { public

    UserManager(SpiceManager spiceManager, ManagerCallback callback) { super(spiceManager, callback); } public void getPublicSession(){ execute(new GetPublicSessionRequest()); } } BaseManager UserManager TripManager PaymentManager
  49. RoboSpice + Retrofit public class SomeActivity extends BaseActivity implements ManagerCallback<Session>

    { private SpiceManager spiceManager = new SpiceManager(MyRetrofitGsonSpiceService.class); private void someMethod(){ // ... UserManager userManager = new UserManager(spiceManager, this); userManager.getPublicSession(); // ... } @Override public void onSuccess(Session result) { } }
  50. Performances

  51. OAuth2 POST /oauth2/access_token?grant_type=password &scope=user_profile &login=username &password=userpwd { "access_token": "abcd1234567890", "expires_in":

    3600, "token_type": "bearer" } Récupération d’un Access Token
  52. OAuth2 Utilisation de l’Access Token GET /api/trips?fn=Paris Authorization: Bearer abcd1234567890

    { "trips": [...] }
  53. OAuth2 Varnish

  54. OAuth2

  55. Latence ?

  56. OAuth2

  57. OAuth2 JWT

  58. http://jwt.io JSON Web Token

  59. Monitoring

  60. None
  61. public SpiceManager(Class<? extends SpiceService> spiceServiceClass, Boolean withMonitoring) { super(spiceServiceClass); this.isMonitored.set(withMonitoring);

    //AtomicBoolean } @Override public synchronized void start(Context context) { super.start(context); if (this.isMonitored.get()) { MonitoringService.getInstance().start(); } } @Override public synchronized void shouldStop() { super.shouldStop(); if (isMonitored.get()) { MonitoringService.getInstance().stop(); } } Network monitoring
  62. private static AtomicInteger monitoredServiceNumber ; private ConcurrentLinkedQueue<MonitoringData> inMemoryQueue ; public

    synchronized void start() { if ( monitoredServiceNumber.getAndIncrement() > 0) { return; } startMonitoring(); } private void flush() { int size = inMemoryQueue.size(); if (size == 0) { return; } MonitoringData[] monitoringDatas = new MonitoringData[size]; for (int i = 0; i < size; i++) { monitoringDatas[i] = inMemoryQueue.poll(); } MonitoringRequest monitoringRequest = new MonitoringRequest(monitoringDatas); spiceManager.execute(monitoringRequest, new MonitoringListener()); } Network monitoring
  63. protected RestAdapter.Builder createRestAdapterBuilder() { RestAdapter.Builder bbcRestAdapter = new RestAdapter.Builder() .setProfiler(new

    MonitoringProfiler()); return bbcRestAdapter; } public class MonitoringProfiler implements Profiler<Object> { @Override public void afterCall(RequestInformation requestInfo, long elapsedTime, int statusCode, Object beforeCallData) { MonitoringService.getInstance().sendReq(requestInfo, elapsedTime, statusCode); } } Network monitoring
  64. Source : SPDY Essentials by Will Chan Roberto Peon SPDY

  65. Crashs

  66. Compte-rendu quotidien sur

  67. 30 jours avant déploiement total d’une nouvelle release

  68. Google+ Group Plus de 150 membres dont une vingtaine de

    très actifs. Des retours chaque semaine sur le fonctionnement produit, des bugs ou des suggestions d’amélioration. Sélection de membres pour tester nos prochaines features.
  69. None
  70. Material Design

  71. Wear

  72. Merci @BlaBlaCarTech