(Tutorial Android) MVP Architecture on Android

Sabtu, Mei 06, 2017 Add Comment
MVP merupakan singkatan dari Model-View-Presenter. MVP adalah arsitektur atau design pattern yang memisahkan antara proses bisnis dan logic di luar dari Activity dan Fragment serta membuatnya menjadi lifecycle tersendiri. Dengan menggunakan arsitektur MVP dapat membuat pengembangan aplikasi Android yang kompleks jadi lebih mudah dan simpel, jumlah kode program menjadi lebih sedikit serta mudah di maintenance.

Source : http://www.tinmegali.com/


Berikut penjelasan dari masing-masing layer di MVP :

View : Layer ini merupakan layer untuk menampilkan data dan interaksi ke user. View biasanya ditempatkan di Activity, Fragment atau Dialog di Android.

Model : Layer ini merupakan layer untuk mengakses data dari local maupun remote server.

Presenter : Layer ini merupakan layer yang menjembatani antara Model dan View. Presenter juga dapat berjalan di background task.

Pada kesempatan kali ini, kita akan coba membuat sebuah aplikasi dengan arsitektur MVP.

Buat project baru kemudian buat sistem package seperti berikut :




Buat Layout 

Pertama kali kita membuat layout terlebih dahulu. Di sini kita akan membuat dua buah layout diantaranya :

1. activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/activity_vertical_margin">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:padding="@dimen/activity_horizontal_margin">

        <EditText
            android:id="@+id/et_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/email_hint"
            android:inputType="textEmailAddress" />

        <EditText
            android:id="@+id/et_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/password_hint"
            android:inputType="textPassword" />

    </LinearLayout>

    <Button
        android:text="@string/login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="loginAct"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

2. 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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wimsonevel.androidmvp.MainActivity">

    <TextView
        android:id="@+id/tv_welcome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/welcome"
        android:textSize="24sp" />

    <TextView
        android:id="@+id/tv_email"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_welcome"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="12dp" />

</RelativeLayout>

Selanjutnya kita akan membuat kerangka Model-View-Presenter.

Model 

Buat package dengan nama model, kemudian buat kelas java dengan nama User.java
package com.wimsonevel.androidmvp.model;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by Wim on 12/31/16.
 */

public class User implements Parcelable {

    private String email;

    public User(String email) {
        this.email = email;
    }

    protected User(Parcel in) {
        this.email = in.readString();
    }

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

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

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

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

}

View

Buat package dengan nama view, kemudian buat beberapa interface view berikut :
BaseView.java
package com.wimsonevel.androidmvp.view;

import android.content.Context;

/**
 * Created by Wim on 12/31/16.
 */

public interface BaseView {

    Context getContext();

}
LoginView.java
package com.wimsonevel.androidmvp.view;

import com.wimsonevel.androidmvp.model.User;

/**
 * Created by Wim on 1/1/17.
 */

public interface LoginView extends BaseView {

    void onSuccess(User user);
    void onFailed(String msg);

    void showProgress();
    void hideProgress();
}

Presenter 

Buat package dengan nama presenter, kemudian buat beberapa kelas presenter berikut :
BasePresenter.java
package com.wimsonevel.androidmvp.presenter;

/**
 * Created by Wim on 12/31/16.
 */

public interface BasePresenter<V> {

    void attachView(V view);
    void detachView();
    boolean isViewAttached();

}
LoginPresenter.java
package com.wimsonevel.androidmvp.presenter;

import com.wimsonevel.androidmvp.R;
import com.wimsonevel.androidmvp.model.User;
import com.wimsonevel.androidmvp.view.LoginView;

/**
 * Created by Wim on 1/1/17.
 */

public class LoginPresenter implements BasePresenter<LoginView> {

    private LoginView loginView;

    @Override
    public void attachView(LoginView view) {
        this.loginView = view;
    }

    @Override
    public void detachView() {
        this.loginView = null;
    }

    @Override
    public boolean isViewAttached() {
        return loginView != null;
    }

    public void doLogin(String email, String password) {
        loginView.showProgress();

        if(email.equals("example@mail.com") && password.equals("wim")) {
            loginView.hideProgress();
            loginView.onSuccess(new User(email));
        }else{
            loginView.hideProgress();
            loginView.onFailed(loginView.getContext().getResources().getString(R.string.login_failed));
        }

    }
}


Activity

Selanjutnya adalah membuat beberapa activity sebagai berikut :
LoginActivity.java
package com.wimsonevel.androidmvp;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.wimsonevel.androidmvp.model.User;
import com.wimsonevel.androidmvp.presenter.LoginPresenter;
import com.wimsonevel.androidmvp.view.LoginView;

import static android.text.TextUtils.isEmpty;

/**
 * Created by Wim on 1/1/17.
 */

public class LoginActivity extends AppCompatActivity implements LoginView {

    private EditText etEmail;
    private EditText etPassword;

    private LoginPresenter loginPresenter;
    private ProgressDialog progressDialog;

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

        etEmail = (EditText) findViewById(R.id.et_email);
        etPassword = (EditText) findViewById(R.id.et_password);

        progressDialog = new ProgressDialog(this);
        progressDialog.setMessage(this.getResources().getString(R.string.loading));
    }

    void loginAct(View view) {
        loginPresenter = new LoginPresenter();
        loginPresenter.attachView(this);

        if(isEmpty(etEmail.getText().toString())) {
            return;
        }

        if(isEmpty(etPassword.getText().toString())) {
            return;
        }

        if(loginPresenter.isViewAttached()) {
            loginPresenter.doLogin(etEmail.getText().toString(), etPassword.getText().toString());
        }

    }

    @Override
    public void onSuccess(User user) {
        Toast.makeText(this, this.getResources().getString(R.string.login_success), Toast.LENGTH_SHORT).show();
        MainActivity.start(this, user);
    }

    @Override
    public void onFailed(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showProgress() {
        progressDialog.show();
    }

    @Override
    public void hideProgress() {
        progressDialog.dismiss();
    }

    @Override
    public Context getContext() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        loginPresenter.detachView();
    }
}

MainActivity.java
package com.wimsonevel.androidmvp;

import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import com.wimsonevel.androidmvp.model.User;

public class MainActivity extends AppCompatActivity {

    private TextView tvEmail;

    public static void start(Context context, User user) {
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra(MainActivity.class.getSimpleName(), user);
        context.startActivity(intent);
    }

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

        tvEmail = (TextView) findViewById(R.id.tv_email);

        User user = getIntent().getParcelableExtra(MainActivity.class.getSimpleName());
        tvEmail.setText(user.getEmail());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
Terakhir beberapa tambahan resource string di strings.xml
<resources>
    <string name="app_name">Android-MVP-Pattern</string>

    <string name="email_hint">Email</string>
    <string name="password_hint">Password</string>
    <string name="login">Login</string>
    <string name="welcome">Welcome</string>
    <string name="login_success">Login Success!</string>
    <string name="login_failed">Incorrect username or password!</string>
    <string name="loading">Loading...</string>

</resources>

Build dan running maka hasilnya seperti berikut :




Source code lengkap dapat di download di https://github.com/wimsonevel/Android-MVP-Pattern

Sekian tutorial kali ini.
Semoga bermanfaat :)

(Tutorial Android) Bottom NavigationView Android Material Design

Selasa, Desember 13, 2016 4 Comments

Bottom Navigation adalah fitur yang baru-baru ini ditambahkan di Android Design Support versi 25. Bottom Navigation dapat memudahkan kita membuat desain dengan menu tab bar berada di bawah (hampir sama seperti bottom tabs di iOS). Jika sebelumnya kita membuat bottom bar memakai tambahan library third party, kini Bottom Navigation sudah tersedia di Design Support dan siap diimplementasikan di aplikasi kita.

Pada tutorial kali ini saya akan membahas implementasi dari Bottom NavigationView.
Pertama-tama kita tambahkan dulu design support library versi 25 di gradle.
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:25.0.0'
    compile 'com.android.support:design:25.0.0'
}

Membuat Layout

Buat sebuah layout activity_main.xml dan tambahkan Bottom Navigation View di dalamnya. Karena Bottom Navigation ini letaknya harus di bawah, maka kita harus mendefinisikan letaknya dengan atribut layout_alignParentBottom="true".
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wimso.androidbottomnavigation.MainActivity">

    <FrameLayout
        android:id="@+id/flContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottom_navigation" />

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        app:itemBackground="@color/colorPrimary"
        app:itemIconTint="@color/colorWhite"
        app:itemTextColor="@color/colorWhite"
        app:menu="@menu/bottom_menu"/>

</RelativeLayout>
Beberapa atribut yang ada di Bottom Navigation :
- app:itemBackground - Warna background pada bottom navigation menu
- app:itemIconTint -Tint yang dapat digunakan pada icon menu
- app:itemTextColor – Warna text pada bottom navigation menu
- app:menu – resource menu untuk menampilkan item pada bottom navigation menu

Kemudian buat beberapa layout lagi untuk menu-menu di Bottom Navigation.

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/home"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/notifications"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/favorites"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/search"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/profile"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

Tambahkan beberapa resources.

strings.xml
<resources>
    <string name="app_name">AndroidBottomNavigation</string>

    <string name="home">Home</string>
    <string name="notifications">Notifications</string>
    <string name="favorites">Favorites</string>
    <string name="search">Search</string>
    <string name="profile">Profile</string>

</resources>
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#F44336</color>
    <color name="colorPrimaryDark">#E53935</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorWhite">#FFFFFF</color>
</resources>


Buat beberapa fragment untuk setiap menu bottom navigation.

HomeFragment.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.wimso.androidbottomnavigation.R;

/**
 * Created by Wim on 11/30/16.
 */
public class HomeFragment extends Fragment {

    public static HomeFragment newInstance() {
        return new HomeFragment();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_home, container, false);
        return view;
    }
    
}

NotificationFragment.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.wimso.androidbottomnavigation.R;
/**
 * Created by Wim on 11/30/16.
 */
public class NotificationFragment extends Fragment {
    public static NotificationFragment newInstance() {
        return new NotificationFragment();
    }
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_notification, container, false);
        return view;
    }
}
FavoritesFragment.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.wimso.androidbottomnavigation.R;
/**
 * Created by Wim on 11/30/16.
 */
public class FavoritesFragment extends Fragment {

    public static FavoritesFragment newInstance() {
        return new FavoritesFragment();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_favorites, container, false);
        return view;
    }

}
SearchFragment.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.wimso.androidbottomnavigation.R;
/**
 * Created by Wim on 11/30/16.
 */
public class SearchFragment extends Fragment {

    public static SearchFragment newInstance() {
        return new SearchFragment();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_search, container, false);
        return view;
    }

}
ProfileFragment.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.wimso.androidbottomnavigation.R;
/**
 * Created by Wim on 11/30/16.
 */
public class ProfileFragment extends Fragment {

    public static ProfileFragment newInstance() {
        return new ProfileFragment();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_profile, container, false);
        return view;
    }

}
Selanjutnya buat MainActivity dan lengkapi code berikut :
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import com.wimso.androidbottomnavigation.fragment.FavoritesFragment;
import com.wimso.androidbottomnavigation.fragment.HomeFragment;
import com.wimso.androidbottomnavigation.fragment.NotificationFragment;
import com.wimso.androidbottomnavigation.fragment.ProfileFragment;
import com.wimso.androidbottomnavigation.fragment.SearchFragment;

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

    BottomNavigationView bottomNavigationView;

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

        if(savedInstanceState == null) {
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.flContent, HomeFragment.newInstance())
                    .commit();
        }

        bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation);
        bottomNavigationView.setOnNavigationItemSelectedListener(this);
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Fragment fragment = null;

        switch (item.getItemId()) {
            case R.id.action_home:
                fragment = HomeFragment.newInstance();
                break;
            case R.id.action_notifications:
                fragment = NotificationFragment.newInstance();
                break;
            case R.id.action_favorites:
                fragment = FavoritesFragment.newInstance();
                break;
            case R.id.action_search:
                fragment = SearchFragment.newInstance();
                break;
            case R.id.action_profile:
                fragment = ProfileFragment.newInstance();
                break;
        }

        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.flContent, fragment)
                .commit();

        return false;
    }

}
Build dan jalankan hasilnya akan tampak sebagai berikut :


Source code lengkap dapat dilihat di https://github.com/wimsonevel/AndroidBottomNavigation

Sekian tutorial kali ini, semoga bermanfaat.
Terima kasih :)

(Tutorial Android) Upload Image using Retrofit

Rabu, November 16, 2016 6 Comments

Upload file atau image dari Android ke server bisa dilakukan dengan berbagai macam cara, salah satunya menggunakan library retrofit.

Pada postingan kali ini saya akan memberikan tutorial upload image menggunakan retrofit.

Pada kasus kali ini kita akan mencoba mengupload image dengan dua metode yakni :

1. Multipart
2. Base64

Saya akan sedikit menjelaskan metode upload image dengan multipart maupun base64 tersebut.

Multipart : Metode yang menggunakan HTTP Request untuk mengirim data ke server. Metode ini biasanya digunakan untuk mengupload file yang sizenya besar.

Base64 : Metode yang digunakan untuk melakukan proses encoding terhadap suatu binary data (contohnya: image). Proses encoding tersebut akan menghasilkan format string. Nah, format string inilah nantinya yang akan dikirim ke server dan dilakukan proses decode untuk mengembalikannya ke binary.

Oke mari kita langsung praktek saja.

Buat API 

- Pertama buat folder di htdocs dengan nama api
- Buat file php dengan nama upload.php, kemudian isikan code berikut :
<?php
 $action = htmlspecialchars($_POST['action']);

 $response = array("success" => FALSE);

 if($action == "multipart") {
     if ($_FILES["photo"]["error"] > 0) {
      $response["success"] = FALSE;
   $response["message"] = "Upload Failed";
     } else {
   $name_file=htmlspecialchars($_FILES['photo']['name']);
   
         if (@getimagesize($_FILES["photo"]["tmp_name"]) !== false) {

    move_uploaded_file($_FILES["photo"]["tmp_name"], $name_file);

    $response["success"] = TRUE;
       $response["message"] = "Upload Successfull";
    
   }else{
    $response["success"] = FALSE;
    $response["message"] = "Upload Failed";
   }

   echo json_encode($response);
     }
 }else if($action == "base64") {
  $photo = htmlspecialchars($_POST['photo']);

  $photo = str_replace('data:image/png;base64,', '', $photo);
  $photo = str_replace(' ', '+', $photo);

  $data = base64_decode($photo);
  $file = uniqid() . '.png';

  file_put_contents($file, $data);

  $response["success"] = TRUE;
  $response["message"] = "Upload Successfull";
  
  echo json_encode($response);
 }

?>
Buat Project Android

Pertama, tambahkan library retrofit dan okhttp3 ke gradle module-level
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.1'

    // Network
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile "com.squareup.okhttp3:okhttp:3.4.1"
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
}

Buat layout dengan nama 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wimso.android_uploadimage.MainActivity">

    <Button
        android:id="@+id/btn_choose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Choose Photo"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <ImageView
        android:id="@+id/img_thumb"
        android:layout_width="match_parent"
        android:layout_height="250dp"
        android:layout_below="@+id/btn_choose"
        android:src="@mipmap/ic_launcher"
        android:scaleType="centerCrop"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        android:id="@+id/btn_upload_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Upload with Multipart"
        android:layout_below="@+id/img_thumb"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        android:id="@+id/btn_upload_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Upload with Base64"
        android:layout_below="@+id/btn_upload_1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

</RelativeLayout>

Buat package dengan nama network.

Buat kelas dengan nama Config.java di package network. Kelas ini berisi URL dan Endpoint dari API.
/**
 * Created by Wim on 11/14/16.
 */
public class Config {

    public static final String BASE_URL = "http://192.168.2.31"; // Your local IP Address
    public static final String API_DIR = "/api";

    public static final String API_UPLOAD = BASE_URL + API_DIR + "/upload.php";

}
Kemudian buat kelas dengan nama BaseResponse.java
/**
 * Created by Wim on 11/14/16.
 */
public class BaseResponse {

    private boolean success;
    private String message;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

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

Kemudian buat sebuah interface dengan nama UploadInterface.java yang berisi method untuk mengupload image ke server. Method untuk mengupload image terdiri dari 2 yakni method dengan teknik multipart dan base46.
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;

/**
 * Created by Wim on 10/6/16.
 */
public interface UploadInterface {

    @Multipart
    @POST(Config.API_UPLOAD)
    Call<BaseResponse> uploadPhotoMultipart(
            @Part("action") RequestBody action,
            @Part MultipartBody.Part photo);

    @FormUrlEncoded
    @POST(Config.API_UPLOAD)
    Call<BaseResponse> uploadPhotoBase64(
            @Field("action") String action,
            @Field("photo") String photo);

}

Kemudian buat kelas UploadService.java untuk konfigurasi retrofit dan method dari interface.
/**
 * Created by Wim on 10/7/16.
 */
public class UploadService {

    private UploadInterface uploadInterface;

    public UploadService() {
        OkHttpClient.Builder okhttpBuilder = new OkHttpClient().newBuilder();
        okhttpBuilder.connectTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.writeTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.readTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.retryOnConnectionFailure(true);

        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            okhttpBuilder.addInterceptor(interceptor);
        }

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

        uploadInterface = retrofit.create(UploadInterface.class);
    }

    public void uploadPhotoMultipart(RequestBody action, MultipartBody.Part photo, Callback callback) {
        uploadInterface.uploadPhotoMultipart(action, photo).enqueue(callback);
    }

    public void uploadPhotoBase64(String action, String photo, Callback callback) {
        uploadInterface.uploadPhotoBase64(action, photo).enqueue(callback);
    }

}

Selanjutnya buat beberapa kelas utility berikut :

FileUtils.java
(Kelas ini saya modifikasi dari https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java )
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created by Wim on 9/29/16.
 */
public class FileUtils {

    /**
     * @return Whether the URI is a local one.
     */
    public static boolean isLocal(String url) {
        if (url != null && !url.startsWith("http://") && !url.startsWith("https://")) {
            return true;
        }
        return false;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     * @author paulburke
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     * @author paulburke
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     * @author paulburke
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Old Google Photos.
     */
    public static boolean isGoogleOldPhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is New Google Photos.
     */
    public static boolean isGoogleNewPhotosUri(Uri uri) {
        return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
    }

    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    public static String getPath(final Context context, final Uri uri) {
        // DocumentProvider
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if(DocumentsContract.isDocumentUri(context, uri)) {
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    if ("primary".equalsIgnoreCase(type)) {
                        return Environment.getExternalStorageDirectory() + "/" + split[1];
                    }

                    // TODO handle non-primary volumes
                }
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {

                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                    return getDataColumn(context, contentUri, null, null);
                }
                // MediaProvider
                else if (isMediaDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    Uri contentUri = null;
                    if ("image".equals(type)) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    }

                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[]{
                            split[1]
                    };

                    return getDataColumn(context, contentUri, selection, selectionArgs);
                }
            }
            // MediaStore (and general)
            else if ("content".equalsIgnoreCase(uri.getScheme())) {

                // Return the remote address
                if (isGoogleOldPhotosUri(uri)) {
                    // return http path, then download file.
                    return uri.getLastPathSegment();
                }
                else if (isGoogleNewPhotosUri(uri)) {
                    if(getDataColumn(context,  uri, null, null) == null) {
                        return getDataColumn(context,  Uri.parse(getImageUrlWithAuthority(context,uri)), null, null);
                    }else{
                        return getDataColumn(context,  uri, null, null);
                    }
                }

                return getDataColumn(context, uri, null, null);
            }
            // File
            else if ("file".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }
        }else{
            String[] proj = { MediaStore.Images.Media.DATA };
            String result = null;

            CursorLoader cursorLoader = new CursorLoader(
                    context,
                    uri, proj, null, null, null);
            Cursor cursor = cursorLoader.loadInBackground();

            if(cursor != null){
                int column_index =
                        cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                cursor.moveToFirst();
                result = cursor.getString(column_index);
            }

            return result;
        }


        return null;
    }

    public static File getFile(Context context, Uri uri) {
        if (uri != null) {
            String path = getPath(context, uri);
            if (path != null && isLocal(path)) {
                return new File(path);
            }
        }
        return null;
    }

    public static String getImageUrlWithAuthority(Context context, Uri uri) {
        InputStream is = null;
        if (uri.getAuthority() != null) {
            try {
                is = context.getContentResolver().openInputStream(uri);
                Bitmap bmp = BitmapFactory.decodeStream(is);

                return writeToTempImageAndGetPathUri(context, bmp).toString();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }finally {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static Uri writeToTempImageAndGetPathUri(Context inContext, Bitmap inImage) {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
        String path = MediaStore.Images.Media.insertImage(inContext.getContentResolver(), inImage, "Title", null);
        return Uri.parse(path);
    }
}
ImageUtils.java
import android.graphics.Bitmap;
import android.util.Base64;

import java.io.ByteArrayOutputStream;

/**
 * Created by Wim on 11/14/16.
 */
public class ImageUtils {

    public static String bitmapToBase64String(Bitmap bmp, int quality) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        byte[] bytes = baos.toByteArray();
        return Base64.encodeToString(bytes, Base64.DEFAULT);
    }

}
Sekarang Buka MainActivity.java dan lengkapi dengan kode berikut :
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import com.wimso.android_uploadimage.network.BaseResponse;
import com.wimso.android_uploadimage.network.UploadService;

import java.io.File;
import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final int PICK_IMAGE = 1;
    private static final int PERMISSION_REQUEST_STORAGE = 2;

    private static final String TYPE_1 = "multipart";
    private static final String TYPE_2 = "base64";

    private ImageView imgThumb;

    private Button btnChoose;
    private Button btnUpload1;
    private Button btnUpload2;

    private UploadService uploadService;
    private Uri uri;

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

        imgThumb = (ImageView) findViewById(R.id.img_thumb);
        btnChoose = (Button) findViewById(R.id.btn_choose);
        btnUpload1 = (Button) findViewById(R.id.btn_upload_1);
        btnUpload2 = (Button) findViewById(R.id.btn_upload_2);

        btnChoose.setOnClickListener(this);
        btnUpload1.setOnClickListener(this);
        btnUpload2.setOnClickListener(this);
    }

    private void choosePhoto() {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED
                && ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    PERMISSION_REQUEST_STORAGE);

        }else{
            openGallery();
        }
    }

    public void openGallery() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, "Select Image"), PICK_IMAGE);
    }

    @Override
    public void onClick(View view) {
        if(view == btnChoose) {
            choosePhoto();
        }else if(view == btnUpload1) {
            if(uri != null) {
                File file = FileUtils.getFile(this, uri);
                uploadMultipart(file);
            }else{
                Toast.makeText(this, "You must choose the image", Toast.LENGTH_SHORT).show();
            }
        }else if(view == btnUpload2) {
            if(uri != null) {
                Bitmap bitmap = null;
                try {
                    bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                String encoded = ImageUtils.bitmapToBase64String(bitmap, 100);
                uploadBase64(encoded);
            }else{
                Toast.makeText(this, "You must choose the image", Toast.LENGTH_SHORT).show();
            }
        }
    }
    private void uploadMultipart(File file) {
        RequestBody photoBody = RequestBody.create(MediaType.parse("image/*"), file);
        MultipartBody.Part photoPart = MultipartBody.Part.createFormData("photo",
                file.getName(), photoBody);

        RequestBody action = RequestBody.create(MediaType.parse("text/plain"), TYPE_1);

        uploadService = new UploadService();
        uploadService.uploadPhotoMultipart(action, photoPart, new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                BaseResponse baseResponse = (BaseResponse) response.body();

                if(baseResponse != null) {
                    Toast.makeText(MainActivity.this, baseResponse.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                t.printStackTrace();
            }
        });
    }

    private void uploadBase64(String imgBase64) {
        uploadService = new UploadService();
        uploadService.uploadPhotoBase64(TYPE_2, imgBase64, new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                BaseResponse baseResponse = (BaseResponse) response.body();

                if(baseResponse != null) {
                    Toast.makeText(MainActivity.this, baseResponse.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                t.printStackTrace();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
            if(data != null) {
                uri = data.getData();

                imgThumb.setImageURI(uri);
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_STORAGE: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    openGallery();
                }

                return;
            }
        }
    }

}
Terakhir, tambahkan permission di AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Build dan jalankan aplikasi kemudian coba upload image dengan 2 metode :



Mari kita lihat image yang sudah ter-upload :


- Image baris pertama merupakan hasil dari upload menggunakan Base64.
- Image baris kedua merupakan hasil dari upload menggunakan Multipart.

Dari kedua image tersebut dapat kita lihat bahwa dengan menggunakan Base64, ukuran image menjadi jauh lebih besar dibandingkan dengan ukuran asli image tersebut. Sedangkan dengan menggunakan Multipart, ukuran image tidak terlalu berpengaruh dari ukuran aslinya.

Ukuran asli image sekitar 55 kb.


Kesimpulannya adalah silahkan tentukan sendiri metode apa yang diinginkan sesuai dengan kebutuhan masing-masing :D

Source code lengkap dapat dilihat di https://github.com/wimsonevel/Android-UploadImage

Terima kasih dan semoga bermanfaat.
Happy Coding :)

(Tutorial Android) Login and Registration with PHP & MySQL

Kamis, November 10, 2016 12 Comments

Login dan Registrasi sangat sering kita temukan di beberapa aplikasi yang berbasis client server. Pada postingan kali ini saya akan memberikan tutorial membuat login dan registrasi di Android dengan PHP & MySQL.

Untuk mengkoneksikan antara aplikasi android dengan server, kita perlu membuat API (Application Programming Interface) sebagai jembatan penghubungnya.

Setelah itu mendesain database sekaligus menginteraksikannya dengan API.

Sebelum kita memulai, pastikan laptop/notebook sudah terinstall XAMPP.

1. Install XAMPP

Download dan install XAMPP di sini https://www.apachefriends.org/index.html

2. Buat Database MySQL

Buka http://localhost/phpmyadmin kemudian buat database baru dengan nama simple_db. Setelah itu buat table dengan nama users yang berisi field-field berikut.


3. Buat API

Langkah selanjutnya adalah membuat API. Dalam hal ini kita membuat API menggunakan PHP 5.

Buka folder xampphtdocs dan buat folder dengan nama simple_api.

Di dalam folder simple_api, buat php file dengan nama connect.php.

Connect.php
<?php
     define('_HOST_NAME','localhost');
     define('_DATABASE_NAME','simple_db');
     define('_DATABASE_USER_NAME','root');
     define('_DATABASE_PASSWORD','');
 
     $MySQLiconn = new MySQLi(_HOST_NAME,_DATABASE_USER_NAME,_DATABASE_PASSWORD,_DATABASE_NAME);
  
     if($MySQLiconn->connect_errno) {
       die("ERROR : -> ".$MySQLiconn->connect_error);
     }


?>
Kemudian buat file register.php dan include-kan connect.php berikut.
<?php

include_once 'connect.php';

$response = array("error" => FALSE);

if (isset($_POST['firstname']) && isset($_POST['lastname']) && isset($_POST['email']) && isset($_POST['password'])) {
 $firstname = htmlspecialchars($_POST['firstname']);
 $lastname = htmlspecialchars($_POST['lastname']);
 $email = htmlspecialchar($_POST['email']);
 $password = htmlspecialchars($_POST['password']);
 
 $encrypted_password = hash("sha256", $password);// encrypted password
    
    $sql = $MySQLiconn->query("SELECT email from users WHERE email = '$email'");

    if(mysqli_num_rows($sql) > 0) {
  $response["error"] = TRUE;
        $response["message"] = "User already existed";

        echo json_encode($response);
    }else{
     $sql = $MySQLiconn->query("INSERT INTO users(firstname, lastname, email, password, created_at) VALUES('$firstname', '$lastname', '$email', '$encrypted_password', NOW())"); 

     if($sql) {
         $response["error"] = FALSE;
         $response["message"] = "Register Successfull";

   echo json_encode($response);
     } else {
      $response["error"] = TRUE;
         $response["message"] = "Register Failure";

   echo json_encode($response);
     }  
 
    }
    
}
?>
Kemudian buat file login.php berikut :
<?php

include_once 'connect.php';

$response = array("error" => FALSE);

if (isset($_POST['email']) && isset($_POST['password'])) {
 
 $email = htmlspecialchars($_POST['email']);
 $password = htmlspecialchars($_POST['password']);

 $encrypted_password = hash("sha256", $password);// encrypted password
        
 $sql = $MySQLiconn->query("SELECT * FROM users WHERE email='$email' AND password='$encrypted_password'");

 if(mysqli_num_rows($sql) > 0){
  while($row = $sql->fetch_array()){
   $response["error"] = FALSE;
       $response["message"] = "Login Successfull";
       $response["data"]["firstname"] = $row['firstname'];
       $response["data"]["lastname"] = $row['lastname'];
       $response["data"]["email"] = $row['email'];
      }

  echo json_encode($response);
   }else{
    $response["error"] = TRUE;
     $response["message"] = "Incorrect Email or Password!";

  echo json_encode($response);
   }
}

?>
4. Buat Android Project

Setelah kita membuat API, berikutnya adalah membuat project baru di Android. Aplikasi yang dibuat nantinya memiliki 3 tampilan yakni Login, Registration dan Dashboard.

Pertama, tambahkan library yang diperlukan di gradle.

Untuk library networking kita menggunakan Retrofit 2. Penjelasan dan implementasi Retrofit 2 dalam dilihat di postingan saya sebelumnya http://wimsonevel.blogspot.co.id/2016/07/tutorial-android-http-client-on-android.html
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.1'

    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-gson:2.1.0'
    compile "com.squareup.okhttp3:logging-interceptor:3.3.0"
}
Buat struktur projectnya sebagai berikut :


Edit dan tambahkan beberapa resources string di strings.xml berikut :
<resources>
    <string name="app_name">AndroidLoginAndRegistration</string>
    <string name="email">Email</string>
    <string name="password">Password</string>
    <string name="login">Login</string>
    <string name="register">Register</string>
    <string name="register_caption">Dont have an account? Register</string>
    <string name="title_activity_register">RegisterActivity</string>
    <string name="first_name">First Name</string>
    <string name="last_name">Last Name</string>
    <string name="logout">Logout</string>
    <string name="greeting">Hi, %s :)</string>
</resources>
Edit dan tambahkan beberapa resources color di color.xml berikut :
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#2196F3</color>
    <color name="colorPrimaryDark">#1565C0</color>
    <color name="colorAccent">#1976D2</color>
    <color name="colorWhite">#FFFFFF</color>
    <color name="colorGray">#E5E5E5</color>
    <color name="colorText">#8A8A8A</color>
</resources>
Edit dan tambahkan beberapa resource drawable berikut :

btn_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="@color/colorWhite"/>
    <corners android:radius="4dp" />
    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
</shape>
btn_normal_2.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="@color/colorPrimary"/>
    <corners android:radius="4dp" />
    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
</shape>
btn_pressed.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="@color/colorGray"/>
    <corners android:radius="4dp" />
    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
</shape>
btn_pressed_2.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    <solid android:color="@color/colorPrimaryDark"/>
    <corners android:radius="4dp" />
    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />
</shape>
btn_background.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
        android:drawable="@drawable/btn_pressed" /> <!-- pressed -->
    <item android:state_focused="true"
        android:drawable="@drawable/btn_normal" /> <!-- focused -->
    <item android:drawable="@drawable/btn_normal" /> <!-- default -->
</selector>
btn_background_2.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
        android:drawable="@drawable/btn_pressed_2" /> <!-- pressed -->
    <item android:state_focused="true"
        android:drawable="@drawable/btn_normal_2" /> <!-- focused -->
    <item android:drawable="@drawable/btn_normal_2" /> <!-- default -->
</selector>
Selanjutnya buat beberapa layout untuk Login, Registrasi dan Dashboard berikut :

activity_login.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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:windowSoftInputMode="adjustResize"
    android:background="@color/colorPrimary"
    tools:context="com.wimso.androidloginandregistration.MainActivity">

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

        <EditText
            android:id="@+id/email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:ems="10"
            android:hint="@string/email"
            android:textColor="@color/colorWhite"
            android:textColorHint="@color/colorWhite"
            android:padding="@dimen/activity_vertical_margin"
            android:theme="@style/WhiteFocus"/>

        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:ems="10"
            android:hint="@string/password"
            android:textColor="@color/colorWhite"
            android:textColorHint="@color/colorWhite"
            android:padding="@dimen/activity_vertical_margin"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:theme="@style/WhiteFocus" />

    </LinearLayout>

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

        <Button
            android:id="@+id/btn_login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/login"
            android:background="@drawable/btn_background"
            android:textColor="@color/colorPrimary"/>

        <TextView
            android:id="@+id/register_caption"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="@string/register_caption"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:gravity="center"
            android:textColor="@color/colorWhite" />

    </LinearLayout>

</RelativeLayout>

activity_register.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"
    android:windowSoftInputMode="adjustResize"
    android:background="@color/colorPrimary"
    tools:context="com.wimso.androidloginandregistration.RegisterActivity">

    <android.support.v7.widget.Toolbar
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="?colorPrimary"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true">

        <EditText
            android:id="@+id/firstname"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:hint="@string/first_name"
            android:textColor="@color/colorWhite"
            android:textColorHint="@color/colorWhite"
            android:ems="10"
            android:padding="@dimen/activity_vertical_margin"
            android:theme="@style/WhiteFocus" />

        <EditText
            android:id="@+id/lastname"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPersonName"
            android:hint="@string/last_name"
            android:textColor="@color/colorWhite"
            android:textColorHint="@color/colorWhite"
            android:ems="10"
            android:padding="@dimen/activity_vertical_margin"
            android:theme="@style/WhiteFocus" />

        <EditText
            android:id="@+id/email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:hint="@string/email"
            android:textColor="@color/colorWhite"
            android:textColorHint="@color/colorWhite"
            android:ems="10"
            android:padding="@dimen/activity_vertical_margin"
            android:theme="@style/WhiteFocus" />

        <EditText
            android:id="@+id/password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:hint="@string/password"
            android:textColor="@color/colorWhite"
            android:textColorHint="@color/colorWhite"
            android:ems="10"
            android:padding="@dimen/activity_vertical_margin"
            android:theme="@style/WhiteFocus" />

    </LinearLayout>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true">

        <Button
            android:id="@+id/btn_register"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/register"
            android:background="@drawable/btn_background"
            android:textColor="@color/colorPrimary" />

    </LinearLayout>

</RelativeLayout>
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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:windowSoftInputMode="adjustResize"
    tools:context="com.wimso.androidloginandregistration.MainActivity">

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

        <TextView
            android:id="@+id/greeting"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textSize="48sp"
            android:textColor="@color/colorText" />


        <TextView
            android:id="@+id/email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/colorText" />

    </LinearLayout>

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

        <Button
            android:id="@+id/btn_logout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/logout"
            android:background="@drawable/btn_background_2"
            android:textColor="@color/colorWhite"/>

    </LinearLayout>

</RelativeLayout>

Buat package network→ config lalu buat kelas dengan nama Config.java. Di kelas ini kita akan mendeklarasikan url dan endpoint pada API yang telah kita buat. Base url yang ditentukan dari IP localhost yang digunakan.
/**
 * Created by Wim on 11/4/16.
 */
public class Config {

    public static final String BASE_URL = "YOUR IP ADDRESS"; // Your Local IP Address

    public static final String API_URL = BASE_URL + "/simple_api";

    public static final String API_LOGIN = API_URL + "/login.php";
    public static final String API_REGISTER = API_URL + "/register.php";

}
Masih di package yang sama, buat kelas dengan nama RetrofitBuilder.java
import com.wimso.androidloginandregistration.BuildConfig;

import java.util.concurrent.TimeUnit;
import android.content.Context;

import okhttp3.Cache;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created by Wim on 11/4/16.
 */
public class RetrofitBuilder {

    public static Retrofit builder(Context context) {
        OkHttpClient.Builder okhttpBuilder = new OkHttpClient().newBuilder();
        okhttpBuilder.connectTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.writeTimeout(60, TimeUnit.SECONDS);
        okhttpBuilder.readTimeout(60, TimeUnit.SECONDS);

        int cacheSize = 10 * 1024 * 1024;
        Cache cache = new Cache(context.getCacheDir(), cacheSize);
        okhttpBuilder.cache(cache);

        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            okhttpBuilder.addInterceptor(interceptor);
        }

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

        return retrofit;
    }
}

Dari package network, buat package interfaces kemudian tambahkan interface LoginInterface.java dan RegisterInterface.java.

LoginInterface.java
import com.wimso.androidloginandregistration.model.User;
import com.wimso.androidloginandregistration.network.config.Config;

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;

/**
 * Created by Wim on 11/3/16.
 */
public interface LoginInterface {

    @FormUrlEncoded
    @POST(Config.API_LOGIN)
    Call<User> login(
            @Field("email") String email,
            @Field("password") String password);

}

RegisterInterface.java
import com.wimso.androidloginandregistration.model.BaseResponse;
import com.wimso.androidloginandregistration.network.config.Config;

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;

/**
 * Created by Wim on 11/4/16.
 */
public interface RegisterInterface {

    @FormUrlEncoded
    @POST(Config.API_REGISTER)
    Call<BaseResponse> register(
            @Field("firstname") String firstname,
            @Field("lastname") String lastname,
            @Field("email") String email,
            @Field("password") String password);

}

Kemudian di package network buat kelas LoginService.java dan RegisterService.java berikut :

LoginService.java
import android.content.Context;

import com.wimso.androidloginandregistration.network.config.RetrofitBuilder;
import com.wimso.androidloginandregistration.network.interfaces.LoginInterface;

import retrofit2.Callback;

/**
 * Created by Wim on 11/4/16.
 */
public class LoginService {

    private LoginInterface loginInterface;

    public LoginService(Context context) {
        loginInterface = RetrofitBuilder.builder(context)
                .create(LoginInterface.class);
    }

    public void doLogin(String email, String password, Callback callback) {
        loginInterface.login(email, password).enqueue(callback);
    }

}
RegisterService.java
import android.content.Context;

import com.wimso.androidloginandregistration.network.config.RetrofitBuilder;
import com.wimso.androidloginandregistration.network.interfaces.RegisterInterface;

import retrofit2.Callback;

/**
 * Created by Wim on 11/4/16.
 */
public class RegisterService {

    private RegisterInterface registerInterface;

    public RegisterService(Context context) {
        registerInterface = RetrofitBuilder.builder(context)
                .create(RegisterInterface.class);
    }

    public void doRegister(String firstname, String lastname, String email, String password, Callback callback) {
        registerInterface.register(firstname, lastname, email, password).enqueue(callback);
    }

}
Kemudian buat package model yang berisi kelas model datanya sesuai dengan response json yang akan di mapping dalam bentuk kelas pojos.

Buat kelas dengan nama BaseResponse.java
/**
 * Created by Wim on 11/4/16.
 */
public class BaseResponse {

    private boolean error;
    private String message;

    public BaseResponse() {
    }

    public boolean isError() {
        return error;
    }

    public void setError(boolean error) {
        this.error = error;
    }

    public String getMessage() {
        return message;
    }

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

Buat kelas dengan nama User.java
/**
 * Created by Wim on 11/4/16.
 */
public class User extends BaseResponse {

    private UserData data;

    public User() {
    }

    public UserData getData() {
        return data;
    }

    public void setData(UserData data) {
        this.data = data;
    }
}

Buat kelas dengan nama UserData.java
/**
 * Created by Wim on 11/4/16.
 */
public class UserData {

    private String firstname;
    private String lastname;
    private String email;

    public UserData() {
    }

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
Selanjutnya buat package util kemudian buat kelas PrefUtil.java untuk menyimpan data di SharedPreferences.
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import com.google.gson.Gson;
import com.wimso.androidloginandregistration.model.User;

/**
 * Created by Wim on 11/3/16.
 */
public class PrefUtil {

    public static final String USER_SESSION = "user_session";

    public static SharedPreferences getSharedPreference(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context);
    }

    public static void putUser(Context context, String key, User user) {
        Gson gson = new Gson();
        String json = gson.toJson(user);
        putString(context, key, json);
    }

    public static User getUser(Context context, String key) {
        Gson gson = new Gson();
        String json = getString(context, key);
        User user = gson.fromJson(json, User.class);
        return user;
    }

    public static void putString(Context context, String key, String value) {
        getSharedPreference(context).edit().putString(key, value).apply();
    }

    public static String getString(Context context, String key) {
        return getSharedPreference(context).getString(key, null);
    }

    public static void clear(Context context) {
        getSharedPreference(context).edit().clear().apply();
    }

}

Buat kelas activity dengan nama LoginActivity.java untuk proses login user.
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.wimso.androidloginandregistration.model.User;
import com.wimso.androidloginandregistration.network.LoginService;
import com.wimso.androidloginandregistration.util.PrefUtil;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

/**
 * Created by Wim on 10/31/16.
 */
public class LoginActivity extends AppCompatActivity {

    private EditText emailText;
    private EditText passwordText;
    private Button btnLogin;
    private TextView registerCaption;

    private LoginService loginService;

    public static void start(Context context) {
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }

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

        if(isSessionLogin()) {
            MainActivity.start(this);
            LoginActivity.this.finish();
        }

        emailText = (EditText) findViewById(R.id.email);
        passwordText = (EditText) findViewById(R.id.password);
        btnLogin = (Button) findViewById(R.id.btn_login);
        registerCaption = (TextView) findViewById(R.id.register_caption);

        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                loginAct();
            }
        });

        String caption = "Dont have an account? <b>Register</b>";
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(Html.fromHtml(caption));
        spannableStringBuilder.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View view) {
                RegisterActivity.start(LoginActivity.this);
            }
        }, caption.indexOf("Register") - 3, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        spannableStringBuilder.setSpan(new ForegroundColorSpan(Color.WHITE), caption
                .indexOf("Register") - 3, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        registerCaption.setText(spannableStringBuilder);
        registerCaption.setMovementMethod(LinkMovementMethod.getInstance());

    }

    void loginAct() {
        String email = emailText.getText().toString();
        String password = passwordText.getText().toString();

        if(TextUtils.isEmpty(email)) {
            emailText.setError("Email cannot be empty!");
            return;
        }

        if(TextUtils.isEmpty(password)) {
            passwordText.setError("Password cannot be empty");
            return;
        }

        loginService = new LoginService(this);
        loginService.doLogin(email, password, new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                User user = (User) response.body();

                if(user != null) {
                    if(!user.isError()) {
                        PrefUtil.putUser(LoginActivity.this, PrefUtil.USER_SESSION, user);
                        MainActivity.start(LoginActivity.this);
                        LoginActivity.this.finish();
                    }

                    Toast.makeText(LoginActivity.this, user.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                Toast.makeText(LoginActivity.this, "An error occurred!", Toast.LENGTH_SHORT).show();
            }
        });
    }

    boolean isSessionLogin() {
        return PrefUtil.getUser(this, PrefUtil.USER_SESSION) != null;
    }
}

Buat kelas dengan nama RegisterActivity.java untuk proses register user.
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.wimso.androidloginandregistration.model.BaseResponse;
import com.wimso.androidloginandregistration.network.RegisterService;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class RegisterActivity extends AppCompatActivity {

    private Toolbar toolbar;

    private EditText firstnameText;
    private EditText lastnameText;
    private EditText emailText;
    private EditText passwordText;

    private Button btnRegister;

    private RegisterService registerService;

    public static void start(Context context) {
        Intent intent = new Intent(context, RegisterActivity.class);
        context.startActivity(intent);
    }

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

        toolbar = (Toolbar) findViewById(R.id.toolbar);

        firstnameText = (EditText) findViewById(R.id.firstname);
        lastnameText = (EditText) findViewById(R.id.lastname);
        emailText = (EditText) findViewById(R.id.email);
        passwordText = (EditText) findViewById(R.id.password);
        btnRegister = (Button) findViewById(R.id.btn_register);

        setSupportActionBar(toolbar);

        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        btnRegister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                registerAct();
            }
        });
    }

    void registerAct() {
        String firstname = firstnameText.getText().toString();
        String lastname = lastnameText.getText().toString();
        String email = emailText.getText().toString();
        String password = passwordText.getText().toString();

        if(TextUtils.isEmpty(firstname)) {
            firstnameText.setError("Firstname cannot be empty !");
            return;
        }

        if(TextUtils.isEmpty(lastname)) {
            firstnameText.setError("Lastname cannot be empty !");
            return;
        }

        if(TextUtils.isEmpty(email)) {
            firstnameText.setError("Email cannot be empty !");
            return;
        }

        if(TextUtils.isEmpty(password)) {
            firstnameText.setError("Password cannot be empty !");
            return;
        }

        registerService = new RegisterService(this);
        registerService.doRegister(firstname, lastname, email, password, new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                BaseResponse baseResponse = (BaseResponse) response.body();

                if(baseResponse != null) {
                    if(!baseResponse.isError()) {
                        LoginActivity.start(RegisterActivity.this);
                        RegisterActivity.this.finish();
                    }

                    Toast.makeText(RegisterActivity.this, baseResponse.getMessage(), Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                Toast.makeText(RegisterActivity.this, "An error occurred!", Toast.LENGTH_SHORT).show();
            }
        });

    }

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

}
Buat kelas MainActivity.java sebagai dashboard user.
import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.wimso.androidloginandregistration.model.User;
import com.wimso.androidloginandregistration.util.PrefUtil;

public class MainActivity extends AppCompatActivity {

    private TextView greeting;
    private TextView email;
    private Button btnLogout;

    public static void start(Context context) {
        Intent intent = new Intent(context, MainActivity.class);
        context.startActivity(intent);
    }

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

        greeting = (TextView) findViewById(R.id.greeting);
        email = (TextView) findViewById(R.id.email);
        btnLogout = (Button) findViewById(R.id.btn_logout);

        User user = PrefUtil.getUser(this, PrefUtil.USER_SESSION);

        greeting.setText(getResources().getString(R.string.greeting, user.getData().getFirstname()));
        email.setText(user.getData().getEmail());

        btnLogout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                logoutAct();

                LoginActivity.start(MainActivity.this);
                MainActivity.this.finish();
            }
        });

    }

    void logoutAct() {
        PrefUtil.clear(this);
    }
}

Terakhir adalah tambahkan permission internet di AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
Build dan jalankan maka hasilnya sebagai berikut :




Source code lengkap dapat dilihat di https://github.com/wimsonevel/AndroidLoginAndRegistration

Sekian tutorial dari saya kali ini.
Semoga bermanfaat.

Happy Coding :)