(Tutorial Android) HTTP Client on Android with Retrofit

Friday, July 22, 2016


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*

Share this :

Previous
Next Post »
32 Komentar
avatar

gak pake api key gan, apinya sendiri free kok

Balas
avatar

Gan kok base urlnya error padahal udah di tambah buildconfig fild di gradle

Balas
avatar

Thanks gan. semoga berkah ilmunya :)

Balas
avatar

Gan mau nanya, kalo apinya yg pake key query nya gmna?

Balas
avatar

Please gan bantu dong saya mau retrieve postingan dari blogger tapi stuck di api interface nya gmna query untuk api yang menggunakan key

Balas
avatar

query apinya seperti apa gan? mungkin bisa di contohin dulu..

Balas
avatar

Mas wim, API di iBacor sekarang udah pake API key, ini gimana ya cara nambahin API key-nya di Retrofit ApiInterface-nya? Mohon bantuannya mas, terima kasih:D

Balas
avatar

oke gan, sekarang udah di update pake API key,, bisa dilihat kembali tutorial di atas. Thanks :)

Balas
avatar

dari ibacornya engga ada api yang buat coming soon ya mas?

Balas
avatar

Mas.. kalo pake Volley, ngambil list data Jam-nya gimana ya gan?

Balas
avatar

beda gan klo pake volley, ane blm bikin tutorialnya hehe

Balas
avatar

gan buat dapetin KEY API nya gimana, di ibachor.com/login gagal

Balas
avatar

Web ibacor ngga bisa diakses ya gan ?

Balas
avatar

mas wim request tutorial retrofit yang pakek google API yang pakek OAuth 2.0...

Balas
avatar

iya gan, kyknya domainnya udh expired

Balas
avatar

ibacornya udh gak bisa diakses lg gan

Balas
avatar

sik ta lah, aku sik sibuk saiki, ntr tak buatin yo ...

Balas
avatar

Gan kalo yang gak pake API key jadi gimana ya gan? Soalnya ane gak ada API Key nya

Balas
avatar

sory gan service dari ibacornya udah mati jadi gak bisa dapet API key

Balas
avatar

kang api key nya nggak bisa di ganti pakai api key yang lain ya (api key di web yang berbeda, contohnya pakai api key TMDB)?

Balas
avatar

API key nya udh gak bisa gan, lihat artikel ini aja contoh lain pake TMDB >> http://wimsonevel.blogspot.co.id/2017/07/re-tutorial-android-http-client-on.html

Balas

Penulisan markup di komentar
  • Silakan tinggalkan komentar sesuai topik. Komentar yang menyertakan link aktif, iklan, atau sejenisnya akan dihapus.
  • Untuk menyisipkan kode gunakan <i rel="code"> kode yang akan disisipkan </i>
  • Untuk menyisipkan kode panjang gunakan <i rel="pre"> kode yang akan disisipkan </i>
  • Untuk menyisipkan quote gunakan <i rel="quote"> catatan anda </i>
  • Untuk menyisipkan gambar gunakan <i rel="image"> URL gambar </i>
  • Untuk menyisipkan video gunakan [iframe] URL embed video [/iframe]
  • Kemudian parse kode tersebut pada kotak di bawah ini
  • © 2015 Simple SEO ✔