(Tutorial Android) HTTP Client on Android with Retrofit



Artikel ini sudah tidak relevan lagi dikarenakan service dari ibacor.com sudah mati. Jadi saya mengharapkan para pembaca untuk memakluminya jika aplikasi tidak bisa dijalankan. Untuk tutorial yang baru bisa dilihat di sini http://wimsonevel.blogspot.co.id/2017/07/re-tutorial-android-http-client-on.html

Retrofit merupakan library HTTP Client untuk Android dan Java. Library ini sangat populer dan banyak digunakan oleh developer khususnya Android. Retrofit sangat mudah digunakan, simpel, extensibility dan memiliki performance yang sangat bagus jika dibandingkan dengan library yang lain. Dengan menggunakan Retrofit, kita melakukan request ke REST Webservice dengan mudah dengan berbagai macam method-method yang disediakan. Library Retrofit dikembangkan oleh Square. Saya sendiri suka dengan library ini dan juga sering menggunakannya di setiap project-project yang saya kerjakan. :D

Pada postingan kali ini saya akan membahas tentang penggunaan library Retrofit di Android.

Hal yang perlu disiapkan terlebih dahulu adalah API service. Di sini saya menggunakan layanan Free API dari ibacor. Selengkapnya bisa dilihat di sini http://ibacor.com/api. API yang akan kita gunakan yaitu Jadwal Bioskop 21.

Contoh requestnya sebagai berikut :

Daftar Kota :

GET http://ibacor.com/api/jadwal-bioskop?k=....

Parameter :
k = API Key yang didapat dari ibacor

Response :
{
"status": "success",
"data": [
{
"id": "32",
"kota": "AMBON"
},
{
"id": "6",
"kota": "BALIKPAPAN"
},
…..
]
}


Semua Jadwal Berdasarkan Id Kota

GET http://ibacor.com/api/jadwal-bioskop?k=...&id=10

Parameter :
k = API Key yang didapat dari ibacor
id = ID Kota

Response :
{
"status": "success",
"kota": "JAKARTA",
"date": "22\/07\/16",
"data": [
{
"movie": "GHOSTBUSTERS",
"poster": "http:\/\/image.tmdb.org\/t\/p\/w300\/4qnJ1hsMADxzwnOmnwjZTNp0rKT.jpg",
"genre": "Action, Comedy, Sci-fi",
"duration": "116 minute",
"jadwal": [
{
"bioskop": "ANGGREK XXI",
"jam": [
"12:15",
"14:35",
"15:25",
"16:55",
"19:15",
"20:15",
"21:35"
],
"harga": "Rp.50,000"
},
…...
]
…..
}
]
}

Kita akan membuat aplikasi untuk mengetahui informasi jadwal bioskop 21 di seluruh kota di Indonesia.

Buat project baru.




Pertama, tambahkan library retrofit di gradle. Retrofit yang akan digunakan adalah Retrofit 2. Latest version bisa dilihat di http://square.github.io/retrofit/
compile 'com.squareup.retrofit2:retrofit:2.1.0'
Dan tambahkan juga converter gson, karena json hasil request akan langsung di convert ke gson object.
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
Tambahkan juga beberapa library pendukung yang diperlukan selengkapnya seperti ini :
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:design:23.3.0'

compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'

compile 'com.squareup.picasso:picasso:2.5.2'
}

Inisialisasi BASE_URL dan API_KEY sebagai di gradle.properties
BASE_URL = "http://ibacor.com"
API_KEY = "YOUR API KEY"

Kemudian letakkan configurasi BASE_URL dan API_KEY di gradle.
...
defaultConfig {
applicationId "example.wim.androidretrofit"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}

buildTypes.each {
it.buildConfigField 'String', 'BASE_URL', BASE_URL
it.buildConfigField 'String', 'API_KEY', API_KEY
}

...
Kita akan mulai menambahkan resource yang diperlukan.

values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#795548</color>
<color name="colorPrimaryDark">#4E342E</color>
<color name="colorAccent">#FF4081</color>
<color name="colorWhite">#FFFFFF</color>
<color name="colorGrey">#E5E5E5</color>
</resources>

values/string.xml
<resources>
<string name="app_name">Cinema XXI</string>
<string name="genre">Genre : %s</string>
<string name="duration">Duration : %s</string>
</resources>

drawable/bg_rounded.xml
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimary"/>
<stroke android:width="1dip" android:color="@color/colorPrimary" />
<corners android:radius="3dip"/>
<padding android:left="0dip" android:top="0dip" android:right="0dip" android:bottom="0dip" />
</shape>

Buat beberapa layout berikut :

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="example.wim.androidretrofit.MainActivity">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_city"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>

activity_movie.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_movie"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

activity_showtime.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_showtime"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</LinearLayout>

list_item_city.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:padding="16dp">
<TextView
android:id="@+id/city"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="Jakarta" />
</LinearLayout>

list_item_movie.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:padding="@dimen/margin_8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/poster"
android:layout_width="80dp"
android:layout_height="100dp"
android:src="@drawable/star_trek"
android:scaleType="centerCrop"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="STAR TREK BEYOND"
android:textSize="17sp"
android:textStyle="bold"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/genre"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/genre"
android:textSize="16sp"/>
<TextView
android:id="@+id/duration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/duration"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>

list_item_showtime.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/margin_8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/margin_8dp"
android:background="@color/colorGrey">
<TextView
android:id="@+id/theater"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ATRIUM XXI"
android:textSize="17sp"
android:textStyle="bold"/>
<example.wim.androidretrofit.util.FlowLayout
android:id="@+id/lyTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="vertical"/>
<TextView
android:id="@+id/price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_8dp"
android:text="Rp. 30.000"
android:textSize="16sp"/>
</LinearLayout>
</LinearLayout>

list_item_time.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:orientation="vertical">
<TextView
android:id="@+id/time"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_rounded"
android:gravity="center"
android:padding="4dp"
android:text="00:00"
android:textColor="@color/colorWhite"/>
</LinearLayout>


Selanjutnya kita akan melakukan deklarasi API dengan Retrofit.

Deklarasi API di Retrofit menggunakan Annotations di dalam method Interface dan parameter-parameter yang diperlukan untuk melakukan request. Setiap method harus mempunyai HTTP Annotation seperti GET, POST, PUT, DELETE dan HEAD. Setiap URL resource ditentukan oleh Annotation.

Contohnya :
@GET(“users/list”)
@POST(“users/new”)

Selain itu, parameter-parameter di method berupa annotations juga seperti @Query, @Path, @Body dan @Header.

@Path : variabel substitusi untuk endpoint API.
Contohnya : user id bisa disisipi {id} di URLnya.

@Query : menentukan parameter berupa key dan value.
@Body : mengirimkan request body berupa object melalui POST.
@Header : menentukan header.

Selanjutnya membuat interface yang berisi method-method tadi. Di method tersebut akan digunakan untuk mengirim request berupa daftar kota dan jadwal bioskop.
package example.wim.androidretrofit.service;
import example.wim.androidretrofit.model.City;
import example.wim.androidretrofit.model.Movie;

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

/**
* Created by Wim on 7/19/16.
*/
public interface ApiInterface {

@GET("api/jadwal-bioskop")
Call<City> getCity();

@GET("api/jadwal-bioskop")
Call<Movie> getMovie(
@Query("id") String id);
}


Kemudian buat kelas service dengan nama ApiService. Di sini kita akan membuat konfigurasi dari Retrofit.
package example.wim.androidretrofit.service;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import example.wim.androidretrofit.BuildConfig;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.Callback;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
* Created by Wim on 7/19/16.
*/
public class ApiService {

private ApiInterface apiInterface;

public ApiService(){
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(builder())
.addConverterFactory(GsonConverterFactory.create())
.build();

apiInterface = retrofit.create(ApiInterface.class);
}

private OkHttpClient builder() {
OkHttpClient.Builder okHttpClient = new OkHttpClient().newBuilder();
okHttpClient.connectTimeout(20, TimeUnit.SECONDS);
okHttpClient.writeTimeout(20, TimeUnit.SECONDS);
okHttpClient.readTimeout(90, TimeUnit.SECONDS);


okHttpClient.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url()
.newBuilder()
.addQueryParameter("k", BuildConfig.API_KEY)
.build();

request = request.newBuilder().url(url).build();
return chain.proceed(request);
}
});

return okHttpClient.build();
}

public void getCityList(Callback callback){
apiInterface.getCity().enqueue(callback);
}

public void getMovieList(String id, Callback callback){
apiInterface.getMovie(id).enqueue(callback);
}

}


Sampai di sini kita sudah membuat kelas untuk melakukan request ke API. Selanjutnya adalah membuat kelas model berdasarkan data di JSON. Berikut kelas-kelasnya :

City.java
package example.wim.androidretrofit.model;

import java.util.List;

/**
* Created by Wim on 7/19/16.
*/
public class City {

private String status;
private String message;
private List<CityData> data;

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public List<CityData> getData() {
return data;
}

public void setData(List<CityData> data) {
this.data = data;
}
}

CityData.java
package example.wim.androidretrofit.model;
import android.os.Parcel;
import android.os.Parcelable;

/**
* Created by Wim on 7/19/16.
*/
public class CityData implements Parcelable {

private String id;
private String kota;

public CityData() {
}

protected CityData(Parcel in) {
this.id = in.readString();
this.kota = in.readString();
}

public static final Parcelable.Creator<CityData> CREATOR = new Parcelable.Creator<CityData>() {
@Override
public CityData createFromParcel(Parcel source) {
return new CityData(source);
}

@Override
public CityData[] newArray(int size) {
return new CityData[size];
}
};

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getKota() {
return kota;
}

public void setKota(String kota) {
this.kota = kota;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeString(this.kota);
}

}

Movie.java
package example.wim.androidretrofit.model;

import java.util.List;

/**
* Created by Wim on 7/19/16.
*/
public class Movie {

private String status;
private String message;
private String kota;
private String date;
private List<MovieData> data;

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public String getKota() {
return kota;
}

public void setKota(String kota) {
this.kota = kota;
}

public String getDate() {
return date;
}

public void setDate(String date) {
this.date = date;
}

public List<MovieData> getData() {
return data;
}

public void setData(List<MovieData> data) {
this.data = data;
}
}


MovieData.java
package example.wim.androidretrofit.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;

/**
* Created by Wim on 7/19/16.
*/

public class MovieData implements Parcelable {

private String movie;
private String poster;
private String genre;
private String duration;
private List<Showtime> jadwal;

public MovieData() {
}

protected MovieData(Parcel in) {
this.movie = in.readString();
this.poster = in.readString();
this.genre = in.readString();
this.duration = in.readString();
this.jadwal = new ArrayList<Showtime>();
in.readList(this.jadwal, Showtime.class.getClassLoader());
}

public static final Parcelable.Creator<MovieData> CREATOR = new Parcelable.Creator<MovieData>() {
@Override
public MovieData createFromParcel(Parcel source) {
return new MovieData(source);
}
@Override
public MovieData[] newArray(int size) {
return new MovieData[size];
}
};

public String getMovie() {
return movie;
}

public void setMovie(String movie) {
this.movie = movie;
}

public String getPoster() {
return poster;
}

public void setPoster(String poster) {
this.poster = poster;
}

public String getGenre() {
return genre;
}

public void setGenre(String genre) {
this.genre = genre;
}

public String getDuration() {
return duration;
}


public void setDuration(String duration) {
this.duration = duration;
}

public List<Showtime> getJadwal() {
return jadwal;
}

public void setJadwal(List<Showtime> jadwal) {
this.jadwal = jadwal;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.movie);
dest.writeString(this.poster);
dest.writeString(this.genre);
dest.writeString(this.duration);
dest.writeList(this.jadwal);
}
}

Showtime.java
package example.wim.androidretrofit.model;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;

/**
* Created by Wim on 7/19/16.
*/
public class Showtime implements Parcelable {

private String bioskop;
private List<String> jam;
private String harga;

public Showtime() {
}

protected Showtime(Parcel in) {
this.bioskop = in.readString();
this.jam = in.createStringArrayList();
this.harga = in.readString();
}

public static final Parcelable.Creator<Showtime> CREATOR = new Parcelable.Creator<Showtime>() {
@Override
public Showtime createFromParcel(Parcel source) {
return new Showtime(source);
}
@Override
public Showtime[] newArray(int size) {
return new Showtime[size];
}
};

public String getBioskop() {
return bioskop;
}

public void setBioskop(String bioskop) {
this.bioskop = bioskop;
}

public List<String> getJam() {
return jam;
}

public void setJam(List<String> jam) {
this.jam = jam;
}

public String getHarga() {
return harga;
}

public void setHarga(String harga) {
this.harga = harga;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.bioskop);
dest.writeStringList(this.jam);
dest.writeString(this.harga);
}
}


Setelah kelas model dibuat, berikutnya membuat kelas-kelas utility.

DividerItemDecoration.java
package example.wim.androidretrofit.util;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

/**
* Created by Wim on 7/18/16.
*/
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;

public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}

public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}

public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}


FlowLayout.java
package example.wim.androidretrofit.util;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import example.wim.androidretrofit.R;

/**
* Created by Wim on 7/21/16.
*/
public class FlowLayout extends ViewGroup {

private int paddingHorizontal;
private int paddingVertical;

public FlowLayout(Context context) {
super(context);
init();
}

public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

private void init() {
paddingHorizontal = getResources().getDimensionPixelSize(R.dimen.margin_8dp);
paddingVertical = getResources().getDimensionPixelSize(R.dimen.margin_8dp);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lineHeight = 0;
// 100 is a dummy number, widthMeasureSpec should always be EXACTLY for FlowLayout
int myWidth = resolveSize(100, widthMeasureSpec);
int wantedHeight = 0;
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
// let the child measure itself
child.measure(
getChildMeasureSpec(widthMeasureSpec, 0, child.getLayoutParams().width),
getChildMeasureSpec(heightMeasureSpec, 0, child.getLayoutParams().height));
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// lineheight is the height of current line, should be the height of the heightest view
lineHeight = Math.max(childHeight, lineHeight);
if (childWidth + childLeft + getPaddingRight() > myWidth) {
// wrap this line
childLeft = getPaddingLeft();
childTop += paddingVertical + lineHeight;
lineHeight = childHeight;
}
childLeft += childWidth + paddingHorizontal;
}
wantedHeight += childTop + lineHeight + getPaddingBottom();
setMeasuredDimension(myWidth, resolveSize(wantedHeight, heightMeasureSpec));
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
int lineHeight = 0;
int myWidth = right – left;

for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
lineHeight = Math.max(childHeight, lineHeight);
if (childWidth + childLeft + getPaddingRight() > myWidth) {
childLeft = getPaddingLeft();
childTop += paddingVertical + lineHeight;
lineHeight = childHeight;
}
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
childLeft += childWidth + paddingHorizontal;
}
}
}


Buat beberapa kelas adapter berikut :

CityListAdapter.java
package example.wim.androidretrofit.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import example.wim.androidretrofit.R;
import example.wim.androidretrofit.listener.RecyclerViewItemClickListener;
import example.wim.androidretrofit.model.CityData;

/**
* Created by Wim on 7/21/16.
*/
public class CityListAdapter extends RecyclerView.Adapter<CityListAdapter.CityViewHolder>{

private List<CityData> cityDataList;
private Context context;
private RecyclerViewItemClickListener recyclerViewItemClickListener;

public CityListAdapter(Context context) {
this.context = context;
cityDataList = new ArrayList<>();
}

private void add(CityData item) {
cityDataList.add(item);
notifyItemInserted(cityDataList.size() - 1);
}

public void addAll(List<CityData> cityDataList) {
for (CityData cityData : cityDataList) {
add(cityData);
}
}

public void remove(CityData item) {
int position = cityDataList.indexOf(item);
if (position > -1) {
cityDataList.remove(position);
notifyItemRemoved(position);
}
}

public void clear() {
while (getItemCount() > 0) {
remove(getItem(0));
}
}

public CityData getItem(int positon){
return cityDataList.get(positon);
}

@Override
public CityViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_city, parent, false);
final CityViewHolder cityViewHolder = new CityViewHolder(view);
cityViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int adapterPos = cityViewHolder.getAdapterPosition();
if (adapterPos != RecyclerView.NO_POSITION) {
if (recyclerViewItemClickListener != null) {
recyclerViewItemClickListener.onItemClick(adapterPos, cityViewHolder.itemView);
}
}
}
});
return cityViewHolder;
}

@Override
public void onBindViewHolder(CityViewHolder holder, int position) {
final CityData cityData = cityDataList.get(position);
holder.city.setText(cityData.getKota());
}

@Override
public int getItemCount() {
return cityDataList.size();
}
public void setRecyclerViewItemClickListener(RecyclerViewItemClickListener recyclerViewItemClickListener) {
this.recyclerViewItemClickListener = recyclerViewItemClickListener;
}

static class CityViewHolder extends RecyclerView.ViewHolder {

TextView city;

public CityViewHolder(View itemView) {
super(itemView);

city = (TextView) itemView.findViewById(R.id.city);
}
}
}

MovieListAdapter.java
package example.wim.androidretrofit.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.List;
import example.wim.androidretrofit.R;
import example.wim.androidretrofit.listener.RecyclerViewItemClickListener;
import example.wim.androidretrofit.model.MovieData;

/**
* Created by Wim on 7/20/16.
*/
public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.MovieViewHolder> {

private List<MovieData> movieDataList;
private Context context;
private RecyclerViewItemClickListener recyclerViewItemClickListener;

public MovieListAdapter(Context context) {
this.context = context;
movieDataList = new ArrayList<>();
}

private void add(MovieData item) {
movieDataList.add(item);
notifyItemInserted(movieDataList.size() - 1);
}

public void addAll(List<MovieData> movieDataList) {
for (MovieData movieData : movieDataList) {
add(movieData);
}
}

public void remove(MovieData item) {
int position = movieDataList.indexOf(item);
if (position > -1) {
movieDataList.remove(position);
notifyItemRemoved(position);
}
}
public void clear() {
while (getItemCount() > 0) {
remove(getItem(0));
}
}

public MovieData getItem(int positon){
return movieDataList.get(positon);
}

@Override
public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_movie, parent, false);
final MovieViewHolder movieViewHolder = new MovieViewHolder(view);
movieViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int adapterPos = movieViewHolder.getAdapterPosition();
if (adapterPos != RecyclerView.NO_POSITION) {
if (recyclerViewItemClickListener != null) {
recyclerViewItemClickListener.onItemClick(adapterPos, movieViewHolder.itemView);
}
}
}
});
return movieViewHolder;
}

@Override
public void onBindViewHolder(MovieViewHolder holder, int position) {
final MovieData movieData = movieDataList.get(position);
Picasso.with(context)
.load(movieData.getPoster())
.into(holder.poster);
holder.title.setText(Html.fromHtml(movieData.getMovie()));
holder.genre.setText(context.getResources().getString(R.string.genre, movieData.getGenre()));
holder.duration.setText(context.getResources().getString(R.string.duration, movieData.getDuration()));
}

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

public void setRecyclerViewItemClickListener(RecyclerViewItemClickListener recyclerViewItemClickListener) {
this.recyclerViewItemClickListener = recyclerViewItemClickListener;
}

static class MovieViewHolder extends RecyclerView.ViewHolder {

ImageView poster;
TextView title;
TextView genre;
TextView duration;

public MovieViewHolder(View itemView) {
super(itemView);

poster = (ImageView) itemView.findViewById(R.id.poster);
title = (TextView) itemView.findViewById(R.id.title);
genre = (TextView) itemView.findViewById(R.id.genre);
duration = (TextView) itemView.findViewById(R.id.duration);
}
}
}

ShowtimeListAdapter.java
package example.wim.androidretrofit.adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import example.wim.androidretrofit.R;
import example.wim.androidretrofit.model.Showtime;
import example.wim.androidretrofit.util.FlowLayout;

/**
* Created by Wim on 7/21/16.
*/
public class ShowtimeListAdapter extends RecyclerView.Adapter<ShowtimeListAdapter.ShowtimeViewHolder>{

private List<Showtime> showtimeList;
private Context context;

public ShowtimeListAdapter(Context context) {
this.context = context;
showtimeList = new ArrayList<>();
}

private void add(Showtime item) {
showtimeList.add(item);
notifyItemInserted(showtimeList.size() - 1);
}

public void addAll(List<Showtime> showtimeList) {
for (Showtime showtime : showtimeList) {
add(showtime);
}
}

public void remove(Showtime item) {
int position = showtimeList.indexOf(item);
if (position > -1) {
showtimeList.remove(position);
notifyItemRemoved(position);
}
}

public void clear() {
while (getItemCount() > 0) {
remove(getItem(0));
}
}

public Showtime getItem(int positon){
return showtimeList.get(positon);
}

@Override
public ShowtimeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_showtime, parent, false);
return new ShowtimeViewHolder(view);
}

@Override
public void onBindViewHolder(ShowtimeViewHolder holder, int position) {
final Showtime showtime = showtimeList.get(position);
holder.theater.setText(showtime.getBioskop());
for (int i=0; i<showtime.getJam().size(); i++) {
View view = LayoutInflater.from(context).inflate(R.layout.list_item_time, holder.lyTime, false);
TextView time = (TextView) view.findViewById(R.id.time);
time.setText(showtime.getJam().get(i));
holder.lyTime.addView(view);
}
holder.price.setText(showtime.getHarga());
}

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

static class ShowtimeViewHolder extends RecyclerView.ViewHolder {

TextView theater;
FlowLayout lyTime;
TextView price;

public ShowtimeViewHolder(View itemView) {
super(itemView);

theater = (TextView) itemView.findViewById(R.id.theater);
lyTime = (FlowLayout) itemView.findViewById(R.id.lyTime);
price = (TextView) itemView.findViewById(R.id.price);
}
}
}


Buat interface untuk listener ketika recycler item di klik.

RecyclerItemClickListener.java
package example.wim.androidretrofit.listener;
import android.view.View;
/**
* Created by Wim on 7/17/16.
*/
public interface RecyclerViewItemClickListener {
void onItemClick(int position, View view);
}


Langkah selanjutnya adalah buat activity berikut :

MainActivity.java
package example.wim.androidretrofit;

import android.os.Handler;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import java.net.SocketTimeoutException;

import example.wim.androidretrofit.adapter.CityListAdapter;
import example.wim.androidretrofit.listener.RecyclerViewItemClickListener;
import example.wim.androidretrofit.model.City;
import example.wim.androidretrofit.service.ApiService;
import example.wim.androidretrofit.util.DividerItemDecoration;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity implements RecyclerViewItemClickListener {

private RecyclerView rvCity;
private SwipeRefreshLayout swipeRefreshLayout;

private LinearLayoutManager linearLayoutManager;
private CityListAdapter cityListAdapter;

private ApiService apiService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

rvCity = (RecyclerView) findViewById(R.id.rv_city);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh);

linearLayoutManager = new LinearLayoutManager(this);
cityListAdapter = new CityListAdapter(this);
cityListAdapter.setRecyclerViewItemClickListener(this);

rvCity.setLayoutManager(linearLayoutManager);
rvCity.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
rvCity.setAdapter(cityListAdapter);

swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshData();
}
});

loadData();
}

private void loadData(){
if (swipeRefreshLayout != null)
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});

apiService = new ApiService();
apiService.getCityList(new Callback() {
@Override
public void onResponse(Call call, Response response) {
City city = (City) response.body();

if(city != null) {
if(city.getStatus().equals("success")) {
cityListAdapter.addAll(city.getData());
}else{
Toast.makeText(MainActivity.this, city.getMessage(), Toast.LENGTH_LONG).show();
}
Log.i("STATUS", city.getStatus());
}else{
Toast.makeText(MainActivity.this, "No Data!", Toast.LENGTH_LONG).show();
}

if (swipeRefreshLayout != null)
swipeRefreshLayout.setRefreshing(false);
}

@Override
public void onFailure(Call call, Throwable t) {
if(t instanceof SocketTimeoutException) {
Toast.makeText(MainActivity.this, "Request Timeout. Please try again!", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(MainActivity.this, "Connection Error!", Toast.LENGTH_LONG).show();
}
Log.i("FAILURE", t.toString());

if (swipeRefreshLayout != null)
swipeRefreshLayout.setRefreshing(false);
}
});
}

private void refreshData(){
new Handler().post(new Runnable() {
@Override
public void run() {
cityListAdapter.clear();
loadData();
}
});
}

@Override
public void onItemClick(int position, View view) {
MovieActivity.start(this, cityListAdapter.getItem(position));
}
}


MovieActivity.java
package example.wim.androidretrofit;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

import java.net.SocketTimeoutException;

import example.wim.androidretrofit.adapter.MovieListAdapter;
import example.wim.androidretrofit.listener.RecyclerViewItemClickListener;
import example.wim.androidretrofit.model.CityData;
import example.wim.androidretrofit.model.Movie;
import example.wim.androidretrofit.service.ApiService;
import example.wim.androidretrofit.util.DividerItemDecoration;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
* Created by Wim on 7/20/16.
*/
public class MovieActivity extends AppCompatActivity implements RecyclerViewItemClickListener {

private RecyclerView rvMovie;
private SwipeRefreshLayout swipeRefreshLayout;

private LinearLayoutManager linearLayoutManager;
private MovieListAdapter movieListAdapter;

private ApiService apiService;
private CityData cityData;
private Movie movie;

public static void start(Context context, CityData cityData) {
Intent intent = new Intent(context, MovieActivity.class);
intent.putExtra(MovieActivity.class.getSimpleName(), cityData);
context.startActivity(intent);
}

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movie);

cityData = getIntent().getParcelableExtra(MovieActivity.class.getSimpleName());

ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(cityData.getKota());
actionBar.setDisplayHomeAsUpEnabled(true);

rvMovie = (RecyclerView) findViewById(R.id.rv_movie);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh);

linearLayoutManager = new LinearLayoutManager(this);
movieListAdapter = new MovieListAdapter(this);
movieListAdapter.setRecyclerViewItemClickListener(this);

rvMovie.setLayoutManager(linearLayoutManager);
rvMovie.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
rvMovie.setAdapter(movieListAdapter);

swipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshData();
}
});

loadData(cityData.getId());
}

private void loadData(String id){
if (swipeRefreshLayout != null)
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
swipeRefreshLayout.setRefreshing(true);
}
});


apiService = new ApiService();
apiService.getMovieList(id, new Callback() {
@Override
public void onResponse(Call call, Response response) {
movie = (Movie) response.body();

if(movie != null){
if(movie.getStatus().equals("success")) {
movieListAdapter.addAll(movie.getData());
}else{
Toast.makeText(MovieActivity.this, movie.getMessage(), Toast.LENGTH_LONG).show();
}
Log.i("STATUS", movie.getStatus());
}else{
Toast.makeText(MovieActivity.this, "No Data!", Toast.LENGTH_LONG).show();
}

if (swipeRefreshLayout != null)
swipeRefreshLayout.setRefreshing(false);
}

@Override
public void onFailure(Call call, Throwable t) {
if(t instanceof SocketTimeoutException) {
Toast.makeText(MovieActivity.this, "Request Timeout. Please try again!", Toast.LENGTH_LONG).show();
}else{
Toast.makeText(MovieActivity.this, "Connection Error!", Toast.LENGTH_LONG).show();
}

Log.i("FAILURE", t.toString());

if (swipeRefreshLayout != null)
swipeRefreshLayout.setRefreshing(false);
}
});
}

private void refreshData(){
new Handler().post(new Runnable() {
@Override
public void run() {
movieListAdapter.clear();
loadData(cityData.getId());
}
});
}

@Override
public void onBackPressed() {
super.onBackPressed();
finish();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}

@Override
public void onItemClick(int position, View view) {
ShowtimeActivity.start(this, movieListAdapter.getItem(position), movie.getDate());
}
}

ShowtimeActivity.java
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.view.MenuItem;

import example.wim.androidretrofit.adapter.ShowtimeListAdapter;
import example.wim.androidretrofit.model.MovieData;

/**
* Created by Wim on 7/21/16.
*/
public class ShowtimeActivity extends AppCompatActivity {

private RecyclerView rvShowtime;

private LinearLayoutManager linearLayoutManager;
private ShowtimeListAdapter showtimeListAdapter;

private MovieData movieData;
private String date;

public static void start(Context context, MovieData movieData, String date) {
Intent intent = new Intent(context, ShowtimeActivity.class);
intent.putExtra(ShowtimeActivity.class.getSimpleName() + ".movie", movieData);
intent.putExtra(ShowtimeActivity.class.getSimpleName() + ".date", date);
context.startActivity(intent);
}

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_showtime);

movieData = getIntent().getParcelableExtra(ShowtimeActivity.class.getSimpleName() + ".movie");
date = getIntent().getStringExtra(ShowtimeActivity.class.getSimpleName() + ".date");

ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(Html.fromHtml(movieData.getMovie()));
actionBar.setSubtitle(date);
actionBar.setDisplayHomeAsUpEnabled(true);

rvShowtime = (RecyclerView) findViewById(R.id.rv_showtime);

linearLayoutManager = new LinearLayoutManager(this);
showtimeListAdapter = new ShowtimeListAdapter(this);

rvShowtime.setLayoutManager(linearLayoutManager);
rvShowtime.setAdapter(showtimeListAdapter);

showtimeListAdapter.addAll(movieData.getJadwal());
}

@Override
public void onBackPressed() {
super.onBackPressed();
finish();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
Langkah terakhir jangan lupa tambahkan permission di AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>

Finally, aplikasi kita sudah jadi sekarang tinggal di build dan running. Berikut screenshotnya :





Source code lengkap dapat dilihat di https://github.com/wimsonevel/Android-JadwalBioskop21
Sekian tutorial dari saya, semoga bermanfaat. :)


*Arigatou*
close
==[ Klik disini 1X ] [ Close ]==