Android trends

Material Design

  • MaterialDesign Style
  • RecyclerView
  • Cardview
  • Design Library
  • Shared Elements
  • Vector Drawable
  • Coordinator Layout

View Binding - Olvidando el findViewById()

@Bind(R.id.first_name)
EditText firstName;
@OnClick(R.id.button_continue)
public void continue(){}

View Binding - Olvidando el findViewById()

@Bind(R.id.first_name)
EditText firstName;
@OnClick(R.id.button_continue)
public void continue(){}

EventBus and Otto

Publisher

Event

post()

EventBus

Event

Event

Subscriber

Subscriber

onEvent(Event e)

onEvent(Event e)

EventBus and Otto

//Creación del modelo de datos
public class MessageEvent { /*Definición del modelo*/ }

//Registrar el componente que recibirá el mensaje
eventBus.register(this);
public void onEvent(MessageEvent event) {
    /* Do something */
};

//Enviar el evento
eventBus.post(event);
//Creación del modelo de datos
public class MessageEvent { /*Definición del modelo*/ }

//Registrar el componente que recibirá el mensaje
bus.register(this);

@Subscribe
public void answerAvailable(MessageEvent event) {
    /* Do something */
};

//Enviar el evento
eventBus.post(event);

EventBus and Otto

Volley & Retrofit

Comunicación con el servidor

Volley

¿Cómo funciona Volley?

The Big Picture

Añadir Request al RequestQueue

Ejecutar el Request de manera asíncrona

Devolver la respuesta al hilo principal

Volley

Ciclo de vida de una petición

Volley - Request Example

StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
            new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
 
        // Manejo de la respuesta
        parseArtistsResponse(response);
 
    }
}, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
         
        // Manejo de errores
        error.printStackTrace();
 
    }
});
 
// Añadir la petición a la cola de peticiones
Volley.newRequestQueue(this).add(stringRequest);

Retrofit

The android rest client

Estructura para tu modelo de Retrofit

Los archivos más importantes

ApiClient.Java

ApiService.Java

models

Retrofit - Api Service

Esta será la interfaz en la que declararemos los metodos de la api

public interface GitHubService {
  @GET("/users/{user}/repos")
  void listRepos(@Path("user") String user, Callback<List<Repo>>);
}

Retrofit - Api Service

Las cabeceras

@GET("/users/{user}/repos")


@GET("/users/list?sort=desc")

Las anotaciones de los métodos, de primera instancia, nos permiten manipular el tipo de método y la URL a la cuál haremos la petición.

Argumentos del método

@POST("/users/new")
void createUser(@Body User user, Callback<User> cb);

Los argumentos que contenga el método de la petición definen por medio de anotaciones qué papel jugará ese argumento.

@GET("/group/{id}/users")
List<User> groupList(@Path("id") int groupId, @Query("sort") String sort);

Retrofit - Api Service

En esta clase definiremos el comportamiento del objeto encargado de realizar las peticiones.
Para un mejor manejo de este objeto utilizamos el patrón Singleton.

APP

Client

Server

Retrofit - Api Client

public class ApiClient {

    public static GithubApiService API_SERVICE;

    public static GithubApiService getInstance() {
        if(API_SERVICE == null){
            RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint("https://api.github.com")
            .setConverter(new GsonConverter(gson))
            .build();

            API_SERVICE = restAdapter.create(GithubApiService.class)

        }

        return API_SERVICE
    } 
}

Retrofit - Api Client

ApiClient.getInstance(this)
         .listRepos("silmood", new Callback<List<Repo>>{
        
         @Override
        public void success(List<Repo> repos, Response response) {
            adapter.addAll(repos);
        }
    
        @Override
        public void failure(RetrofitError error) {
            error.printStackTrace();
        }

    })

Retrofit - Sending Request

Image downloading - Picasso, Glide & Fresco

Picasso.with(context)
  .load(url)
  .resize(50, 50)
  .centerCrop()
  .into(imageView)

Image downloading - Picasso, Glide & Fresco

Glide.with(myFragment)
        .load(url)
        .centerCrop()
        .placeholder(R.drawable.loading_spinner)
        .crossFade()
        .into(myImageView);

Image downloading - Picasso, Glide & Fresco

<com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="20dp"
    android:layout_height="20dp"
    fresco:fadeDuration="300"
    fresco:actualImageScaleType="focusCrop"
    fresco:placeholderImage="@color/wait_color"
    fresco:failureImage="@drawable/error"
    fresco:retryImage="@drawable/retrying"
    fresco:retryImageScaleType="centerCrop"
    fresco:backgroundImage="@color/blue"
    fresco:overlayImage="@drawable/watermark"
    fresco:pressedStateOverlayImage="@color/red"
    fresco:roundAsCircle="false" />
mSimpleDraweeView.setImageURI(uri);

ORMs DB - GreenDAO, DBFlow & Realm

//Creación de la base de datos
new DaoMaster.DevOpenHelper(this, "notes-db", null)

//Preparar los DAOs
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();

//Insertando un elemento a la tabla
Note note = new Note(null, noteText, comment, new Date());
noteDao.insert(note);

//Creación del modelo
Schema schema = new Schema(1, "de.greenrobot.daoexample");

Entity note= schema.addEntity("Note");
note.addIdProperty();
note.addStringProperty("text").notNull();
note.addStringProperty("comment");
note.addDateProperty("date");

new DaoGenerator().generateAll(schema, "../DaoExample/src-gen");


ORMs DB - GreenDAO, DBFlow & Realm

//Creación de la base de datos
@Database(name = ColonyDatabase.NAME, version = ColonyDatabase.VERSION)
public class ColonyDatabase {

  public static final String NAME = "Colonies";

  public static final int VERSION = 1;
}

//Creación de una tabla
@Table(databaseName = ColonyDatabase.NAME)
public class Queen extends BaseModel {

  @Column
  @PrimaryKey(autoincrement = true)
  long id;

  @Column
  String name;

}

//Métodos de escritura sobre la base de datos
TransactionManager.getInstance()
            .addTransaction(new SaveModelTransaction<>(processModelInfo));


TransactionManager.getInstance()
                .addTransaction(new UpdateModelListTransaction(processModelInfo));

TransactionManager.getInstance()
                .addTransaction(new DeleteModelListTransaction(processModelInfo));


//Métodos de lectura
TransactionManager.getInstance().addTransaction(new SelectListTransaction<>(new TransactionListenerAdapter<TestModel.class>() {
     @Override
    public void onResultReceived(List<TestModel> testModels) {
        // on the UI thread, do something here
    }
  }, TestModel.class, condition1, condition2,..);

ORMs DB - GreenDAO, DBFlow & Realm

//Clase modelo
public class User extends RealmObject {

    @PrimaryKey
    private String ame;
    private int age;

    @Ignore
    private int sessionId;

    // Standard getters & setters
}

//Métodos de escritura sobre la base de datos
Realm realm = Realm.getInstance(this);
realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("john@corporation.com");
realm.commitTransaction();


//Métodos de lectura
RealmQuery<User> query = realm.where(User.class);

query.equalTo("name", "John");
query.or().equalTo("name", "Peter");

RealmResults<User> result1 = query.findAll();

Realm

RxAndroid + Retrolambda

Observable<OnTextChangeEvent> userNameText = 
    WidgetObservable.text((EditText) findViewById(R.id.edtUserName));

  userNameText.subscribe( e -> Log.d("[Rx]", e.text().toString()));

Bonus - RxJava + Retrofit

@GET("/v1/search?type=artist")
Observable<ArtistSearchResponse> searchArtist(@Query("q") String query);
public void performSearch(String query) {
        apiService.searchArtist(query)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<ArtistSearchResponse>() {
                    @Override
                    public void call(ArtistSearchResponse artistSearchResponse) {
                        artistSearchResponse.getArtists();
                    }
                }, new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {
                        throwable.printStackTrace();
                    }
                });
    }

RxJava + Retrofit + Retrolambda

@GET("/v1/search?type=artist")
Observable<ArtistSearchResponse> searchArtist(@Query("q") String query);
public void performSearch(String query) {
        apiService.searchArtist(query)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        ArtistSearchResponse::getArtists,
                        Throwable::printStackTrace);
    }
apiService.searchArtist(query)
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(artistSearchResponse -> Observable.from(artistSearchResponse.getArtists()))
                .first()
                .subscribe(Artist::getName);
apiService.searchArtist(query)
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(artistSearchResponse -> Observable.from(artistSearchResponse.getArtists()))
                .last()
                .subscribe(Artist::getName);

RxJava + Retrofit + Retrolambda

@GET("/v1/artist")
Observable<Bio> getBio(@Query("name") String artistName);
apiService.searchArtist(query)
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(artistSearchResponse -> Observable.from(artistSearchResponse.getArtists()))
                .flatMap(artist -> apiService.getArtistBio(artist.getName()))
                .subscribe(bio -> bio.getAutor());

RxJava + Retrofit + Retrolambda

MVP

Una arquitectura más limpia

MVP - Model View Presenter

 

Interactor

 

 

Presenter

 

 

ViewModel

 

Petición de datos

Notificar

Mostrar información

ViewModel

Comunmente implementado por una actividad o fragmento


 @Override
public void setupList() {
    mArtistResultsList.setLayoutManager(new LinearLayoutManager(CONTEXT));
    mArtistResultsList.setAdapter(mResultsAdapter);
}

@Override
public void setupAdapter() {
    mResultsAdapter.setOnItemClickListener(mSearchPresenter);
}

@Override
public void setupSearchInput() {
    mArtistSearchInput.setQueryListener(mSearchPresenter);
}

@Override
public void onQueyChange(String query){
    mPresenter.searchArtists(query);
}

@Override
public void displayFoundArtists(ArrayList<Artist> artists) {
    mResultsAdapter.replace(artists);
}

@Override
public void displayFailedSearch() {
    Toast.makeText(CONTEXT, R.string.failed_search, Toast.LENGTH_SHORT).show();
}

@Override
public void displayNetworkError() {
    Toast.makeText(CONTEXT, R.string.network_error, Toast.LENGTH_SHORT).show();
}

@Override
public void displayServerError() {
    Toast.makeText(CONTEXT, R.string.server_error, Toast.LENGTH_SHORT).show();
}

Recibe las interacciones del usuario con la vista

Presenter

 @Override
    public void searchArtists(String query) {
        searchInteractor.performSearch(query, this);
    }

    @Override
    public void onArtistsFound(ArrayList<Artist> artists) {
        searchView.displayFoundArtists(artists);
    }

    @Override
    public void onFailedSearch() {
        searchView.displayFailedSearch();
    }

    @Override
    public void onNetworkError(RetrofitError error) {
        error.printStackTrace();
        searchView.displayNetworkError();
    }

    @Override
    public void onServerError(RetrofitError error) {
        error.printStackTrace();
        searchView.displayServerError();
    }

Interactor

@Override
    public void performSearch(String query, final ArtistSearchServerCallback callback){
        apiService.searchArtist(query, new Callback<ArtistSearchResponse>() {
            @Override
            public void success(ArtistSearchResponse artistSearchResponse, Response response) {
                if(artistSearchResponse.getArtists().isEmpty())
                    callback.onFailedSearch();

                else
                    callback.onArtistsFound(artistSearchResponse.getArtists());
            }

            @Override
            public void failure(RetrofitError error) {
                if (error.getKind() == RetrofitError.Kind.NETWORK)
                    callback.onNetworkError(error);

                else
                    callback.onServerError(error);
            }
        });
    }

Dependency Injection

 

Interactor

 

 

Presenter

 

 

ViewModel

 

Dependency

Dependency

Dagger 2

public ArtistSearchPresenter(ArtistSearchView view, ArtistSearchInteractor interactor) {
        searchView = view;
        searchInteractor = interactor;
}

Dependencia

Dependencia

Necesitamos de otros objetos para construir un objeto

Dagger 2

Clases que crean instancias de los objetos para la inyección de dependencias

Constituyen una API interna a través de la cual obtenemos los objetos que necesitamos para resolver las dependencias

@module
@component
@inject

Inyecta una dependencia. Comunmente se usa como anotación para un atributo de una clase

Gradle Flavors & Build Variants


android {

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            buildConfigField 'String', 'HOST', '"http://192.168.1.34:8000"'
        }

        debug {
            minifyEnabled false
            buildConfigField 'String', 'HOST', '"http://192.168.1.34:3000"'
        }
    }

}

Gradle Flavors & Build Variants


android {

    productFlavors {
            free {
                packageName 'com.silmood.myapp'
                resValue('color', 'primary' , '"#000"')
            }
     
            paid {
                packageName 'com.silmood.myapp.paid'
                resValue('color', 'primary' , '"#fff"')
            }
        }

}

Go, Dart & Kotlin

That's it!

silmood

@silmood

Android Trends

By Petter Hdz

Android Trends

  • 1,330