Retrofit

qué es Retrofit

Ejemplo: Lista de Pokemons

Ejemplo: Repositorio

Crear una aplicación que pida un usuario y obtenga todos sus repositorios en Github:

https://api.github.com/users/paco-portada/repos

Se mostrará una lista (usando un RecyclerView) con los nombres, descripciones y fechas de actualización de todos sus repositorios.
Cuando se pulse en un elemento de la lista, se abrirá el navegador para mostrar el repositorio elegido.

¿Esquema de red?

 

Creación de la aplicación con Android Studio 4.1 y SDK 28:

– Dar permisos en el manifiesto

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.conexionasincrona">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission-sdk-23 android:name="android.permission.INTERNET" />
    <application
        android:usesCleartextTraffic="true"
    </application>
</manifest>

– Usar view binding

build.gradle:

buildFeatures {
    viewBinding true
}

MainActivity:

// https://developer.android.com/guide/topics/ui/floating-action-button
// https://material.io/components/buttons-floating-action-button/android#using-fabs
// https://material.io/resources/icons/?icon=autorenew&style=baseline

// https://stackoverflow.com/questions/25229124/format-instant-to-string
// https://stackoverflow.com/questions/19112357/java-simpledateformatyyyy-mm-ddthhmmssz-gives-timezone-as-ist
// https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html
// https://www.baeldung.com/java-datetimeformatter

public class MainActivity extends AppCompatActivity implements View.OnClickListener, Callback<ArrayList<Repo>> {

    private ActivityMainBinding binding;
    private ReposAdapter adapter;
    private ArrayList<Repo> repos;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);



    }
}

– Repositorios en GitHub

Extensión JSON para Firefox

https://api.github.com/users/paco-portada/repos

Generate Plain Old Java Objects from JSON or JSON-Schema: jsonschema2pojo

clase Owner?

clase Repo?

– Añadir dependencias:

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    // https://square.github.io/okhttp/#releases
    implementation 'com.squareup.okhttp3:okhttp:4.9.0'
    // https://github.com/square/retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    // https://github.com/google/gson
    implementation 'com.google.code.gson:gson:2.8.6'
    // https://search.maven.org/search?q=g:com.squareup.retrofit2%20a:converter-gson
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    // https://github.com/material-components/material-components-android/releases
    implementation 'com.google.android.material:material:1.2.1'
}

– Creación del layout

Floating Action Button:

Codepath: FAB

Android developer

Material.io: Using fabs

Icon autorenew

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.repositorio.MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="paco-portada"
        app:layout_constraintBottom_toTopOf="@+id/coordinatorLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/coordinatorLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </androidx.recyclerview.widget.RecyclerView>

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:layout_margin="24dp"
            android:src="@drawable/autorenew"
            app:layout_anchor="@id/recyclerView"
            app:layout_anchorGravity="bottom|right|end" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:id="@+id/textView1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:text="Name"
                android:textAppearance="@android:style/TextAppearance.Material.Large"
                android:textColor="@color/colorAccent" />

            <TextView
                android:id="@+id/textView2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:text="Description"
                android:textColor="@color/colorPrimary" />

            <TextView
                android:id="@+id/textView3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:text="CreatedAt"
                android:textColor="@color/colorPrimaryDark" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

– Creación del RecyclerView y el adapter

Using the RecyclerView

Android – RecyclerView: Implementing single item click and long press

Interfaz ClickListener

public interface ClickListener{
    public void onClick(View view, int position);
    public void onLongClick(View view,int position);
}

RecyclerTouchListener.java

public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener{

    public ClickListener clicklistener;
    private GestureDetector gestureDetector;

    public RecyclerTouchListener(Context context, final RecyclerView recycleView, final ClickListener clicklistener){

        this.clicklistener=clicklistener;
        gestureDetector=new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View child=recycleView.findChildViewUnder(e.getX(),e.getY());
                if(child!=null && clicklistener!=null){
                    clicklistener.onLongClick(child,recycleView.getChildAdapterPosition(child));
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        View child=rv.findChildViewUnder(e.getX(),e.getY());
        if(child!=null && clicklistener!=null && gestureDetector.onTouchEvent(e)){
            clicklistener.onClick(child,rv.getChildAdapterPosition(child));
        }

        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }
}

ReposAdapter.java

public class ReposAdapter extends RecyclerView.Adapter<ReposAdapter.MyViewHolder> {
    public ArrayList<Repo> mRepos;

    public  ReposAdapter(){
        this.mRepos = new ArrayList<>();
    }

    public void setRepos(ArrayList<Repo> repos) {
        mRepos = repos;
        notifyDataSetChanged();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        ItemViewBinding binding;//Name of the item_view.xml in camel case + "Binding"

        public MyViewHolder(ItemViewBinding b) {
            super(b.getRoot());
            binding = b;
        }
    }

    @NonNull
    @Override
    public ReposAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(ItemViewBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull ReposAdapter.MyViewHolder holder, int position) {
        // Get the data model based on position
        Repo repo = mRepos.get(position);

        holder.binding.textView1.setText(repo.getName());
        holder.binding.textView2.setText(repo.getDescription());
        // holder.binding.textView3.setText(repo.getCreatedAt());
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
                                                       .withZone(ZoneId.systemDefault());
        // API 26 mínima para usar parse
        Instant instant = Instant.parse(repo.getCreatedAt());
        String output = formatter.format( instant );
        holder.binding.textView3.setText(output);
    }

    @Override
    public int getItemCount() {
        return mRepos.size();
    }
}

MainActivity.java:

binding.fab.setOnClickListener(this);

adapter = new ReposAdapter();
binding.recyclerView.setAdapter(adapter);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));

//manage click
binding.recyclerView.addOnItemTouchListener(new RecyclerTouchListener(this, binding.recyclerView, new ClickListener() {
    @Override
    public void onClick(View view, final int position) {
        //Values are passing to activity & to fragment as well
        //Toast.makeText(getApplicationContext(), "Single Click on position        :" + position, Toast.LENGTH_SHORT).show();
        showMessage("Single Click on position:" + position);
        //Uri uri = Uri.parse((String) repos.get(position).getUrl());
        Uri uri = Uri.parse((String) repos.get(position).getHtmlUrl());
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        if (intent.resolveActivity(getPackageManager()) != null)
            startActivity(intent);
        else
            //Toast.makeText(getApplicationContext(), "No hay un navegador", Toast.LENGTH_SHORT).show();
            showMessage("No hay un navegador");
    }

    @Override
    public void onLongClick(View view, int position) {
        //Toast.makeText(getApplicationContext(), "Long press on position :" + position, Toast.LENGTH_LONG).show();
        showMessage("Long press on position :" + position);
    }
}));

//retrofit

    private void showMessage(String s) {
        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}

– Retrofit

Consuming APIs with Retrofit

Cómo consumir una API y procesar la respuesta usando Retrofit

Customize Network Timeouts

Patrón Singleton

Define the endpoints:

public interface MyApiEndpointInterface {

    // Request method and URL specified in the annotation

    @GET("users/{username}")
    Call<User> getUser(@Path("username") String username);

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

    @POST("users/new")
    Call<User> createUser(@Body User user);
}

Creating the Retrofit instance:

Gson gson = new GsonBuilder()
        .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
        .create();

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build();

ApiService.java

public interface ApiService {
    @GET("users/{username}/repos")
    Call<ArrayList<Repo>> listRepos(@Path("username") String username);

    // uso con servidor web local
    @GET("ficheros/repositorios.json")
    Call<ArrayList<Repo>> getRepos();
}

ApiAdapter.java

public class ApiAdapter {
    private static ApiService API_SERVICE;

    public static final String BASE_URL = "https://api.github.com/";
    // android:usesCleartextTraffic="true"
    // public static final String BASE_URL = "http://192.168.0.10/";

    public static synchronized ApiService getInstance() {

        if (API_SERVICE == null) {
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(10, TimeUnit.SECONDS)
                    .writeTimeout(5, TimeUnit.SECONDS)
                    .build();

            Gson gson = new GsonBuilder()
                    .setDateFormat("dd-MM-yyyy")
                    .create();

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .client(okHttpClient)
                    .build();

            API_SERVICE = retrofit.create(ApiService.class);
        }
        return  API_SERVICE;
    }
}

onClick:

public void onClick(View view) {

        if (view == binding.fab) {
            String username = binding.editText.getText().toString();
            showMessage(username);
            if (username.isEmpty())
                showMessage("Debe dar un nombre");
            else {
                // poner cuadro de progreso, si se desea
                //Call<ArrayList<Repo>> call = apiService.listRepos(username);
                Call<ArrayList<Repo>> call = ApiAdapter.getInstance().listRepos(username);
                // uso con servidor web local
                // Call<ArrayList<Repo>> call = ApiAdapter.getInstance().getRepos();
                call.enqueue(this);
            }
        }
    }

onResponse?

@Override
public void onResponse(Call<ArrayList<Repo>> call, Response<ArrayList<Repo>> response) {


}

onFailure?

@Override
public void onFailure(Call<ArrayList<Repo>> call, Throwable t) {

}

Más información:

RetrofitDemo-Tutorial Series (kotlin)

Deja una respuesta