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

Développer une app Android en 2015

Développer une app Android en 2015

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

Alexandra Tritz

March 03, 2015
Tweet

More Decks by Alexandra Tritz

Other Decks in Programming

Transcript

  1. Développer une
    app Android en 2015

    View Slide

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

    View Slide

  3. Mobile First
    Jan. 2013 → 2015

    View Slide

  4. View Slide

  5. View Slide

  6. Librairies utilisées

    View Slide

  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);
    // …

    View Slide

  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){
    // ...
    }

    View Slide

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

    View Slide

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

    View Slide

  11. Retrofit
    A type-safe REST client
    for Android and Java

    View Slide

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

    View Slide

  13. Otto
    // Publishing
    BusManager.getInstance().bus.post(
    new NotificationCountEvent(result)
    );

    View Slide

  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
    }

    View Slide

  15. Autres...
    Crouton
    ViewPagerIndicator

    View Slide

  16. Traductions
    Over The Air

    View Slide

  17. OpenLocalization

    View Slide

  18. openl10n.io

    View Slide

  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)

    View Slide

  20. Les traductions sont mises
    à jour dynamiquement à l’
    ouverture de l’application

    View Slide

  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",
    ...
    }

    View Slide

  22. ids_ext_strings.xml

    MainActivity.java
    BlablacarApplication.getExtString(
    this.getContext(),
    R.id.str_contact.item.text.call_phone);
    activity_main.xml
    typeandroid:text="@id/str_contact.item.text.call_phone"/>

    View Slide

  23. Configuration localisée

    View Slide

  24. View Slide

  25. View Slide

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

    View Slide

  27. Values-fr/context.xml


    fr_FR
    EUR
    false
    true
    true
    GTM-XXXXXX
    false

    getResources().getString(R.string.locale);

    View Slide

  28. Now

    View Slide

  29. Deeplink

    View Slide

  30. View Slide

  31. blablacar://pwd

    View Slide

  32. mobiledeeplinking.org

    View Slide

  33. Schéma
    AndroidManifest.xml



    ...

    View Slide

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

    View Slide

  35. Deeplink

    View Slide

  36. Librairies

    View Slide

  37. Exécute des requêtes asynchrones
    Respecte le cycle de vie des activités
    Cache les réponses

    View Slide

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

    View Slide

  39. RoboSpice
    // Request
    public class MySpiceRequest extends SpiceRequest {
    public MySpiceRequest(Class clazz) {
    super(clazz);
    }
    @Override
    public T loadDataFromNetwork() throws Exception {
    // Background process
    }
    }

    View Slide

  40. RoboSpice
    // Listener
    public class MyRequestListener implements RequestListener {
    @Override
    public void onRequestFailure(SpiceException spiceException) {
    }
    @Override
    public void onRequestSuccess(T t) {
    }
    }

    View Slide

  41. +
    Retrofit

    View Slide

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

    View Slide

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

    View Slide

  44. RoboSpice + Retrofit
    // Interface:
    public class GetPublicSessionRequest
    extends RetrofitSpiceRequest {
    public GetPublicSessionRequest(Class clazz,
    Class interfaceClass) {
    super(clazz, interfaceClass);
    }
    @Override
    public Session loadDataFromNetwork() throws Exception {
    return getService().getPublicSession();
    }
    }

    View Slide

  45. RoboSpice + Retrofit
    // Callback Interfaces
    public interface ManagerErrorCallback {
    public void onFailed(BlablacarError error);
    public void onFailed(List error);
    public void onNoNetworkError();
    }
    public interface ManagerCallback extends ManagerErrorCallback {
    public void onSuccess(T result);
    }

    View Slide

  46. RoboSpice + Retrofit
    public class BaseActivity extends Activity implements ManagerErrorCallback
    {
    // ...
    }
    public class SomeActivity extends BaseActivity implements ManagerCallback
    {
    // ...
    }

    View Slide

  47. RoboSpice + Retrofit
    public class BaseManager implements RequestListener {
    protected ManagerCallback callback;
    protected SpiceManager spiceManager;
    protected BaseManager(SpiceManager spiceManager, ManagerCallback 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 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

    View Slide

  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

    View Slide

  49. RoboSpice + Retrofit
    public class SomeActivity extends BaseActivity implements ManagerCallback {
    private SpiceManager spiceManager = new SpiceManager(MyRetrofitGsonSpiceService.class);
    private void someMethod(){
    // ...
    UserManager userManager = new UserManager(spiceManager, this);
    userManager.getPublicSession();
    // ...
    }
    @Override
    public void onSuccess(Session result) {
    }
    }

    View Slide

  50. Performances

    View Slide

  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

    View Slide

  52. OAuth2
    Utilisation de l’Access Token
    GET /api/trips?fn=Paris
    Authorization: Bearer abcd1234567890
    {
    "trips": [...]
    }

    View Slide

  53. OAuth2
    Varnish

    View Slide

  54. OAuth2

    View Slide

  55. Latence ?

    View Slide

  56. OAuth2

    View Slide

  57. OAuth2
    JWT

    View Slide

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

    View Slide

  59. Monitoring

    View Slide

  60. View Slide

  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

    View Slide

  62. private static AtomicInteger monitoredServiceNumber ;
    private ConcurrentLinkedQueue 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

    View Slide

  63. protected RestAdapter.Builder createRestAdapterBuilder() {
    RestAdapter.Builder bbcRestAdapter = new RestAdapter.Builder()
    .setProfiler(new MonitoringProfiler());
    return bbcRestAdapter;
    }
    public class MonitoringProfiler implements Profiler {
    @Override
    public void afterCall(RequestInformation requestInfo, long elapsedTime,
    int statusCode, Object beforeCallData) {
    MonitoringService.getInstance().sendReq(requestInfo, elapsedTime, statusCode);
    }
    }
    Network monitoring

    View Slide

  64. Source : SPDY Essentials by Will Chan Roberto Peon
    SPDY

    View Slide

  65. Crashs

    View Slide

  66. Compte-rendu quotidien sur

    View Slide

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

    View Slide

  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.

    View Slide

  69. View Slide

  70. Material Design

    View Slide

  71. Wear

    View Slide

  72. Merci
    @BlaBlaCarTech

    View Slide