Tutorial Android #3: Halaman Login dan Registrasi Nasabah
Halo para penjaga pintu aplikasi! Sekarang kita akan membuat halaman login dan registrasi untuk nasabah. Kita akan desain layout yang cantik, validasi input, hubungkan ke API Laravel, dan simpan token autentikasi. Setelah ini, pengguna bisa masuk dan menikmati fitur aplikasi. Siap jadi resepsionis digital?
Apa yang akan kita lakukan?
- ✅ Membuat dua activity: LoginActivity dan RegisterActivity.
- ✅ Desain layout dengan EditText dan Button menggunakan LinearLayout/ConstraintLayout.
- ✅ Menambahkan validasi input (tidak kosong, email valid, password minimal 6 karakter, konfirmasi password).
- ✅ Membuat model class untuk request dan response login/register.
- ✅ Menambahkan endpoint di ApiService untuk login dan register.
- ✅ Memanggil API menggunakan Retrofit dan menangani response.
- ✅ Menyimpan token dan data user ke SharedPreferences.
- ✅ Navigasi ke dashboard sesuai role (setelah login).
- ✅ Menampilkan notifikasi (Toast/AlertDialog) untuk sukses/gagal.
Langkah 1: Membuat Activity Baru
Buat dua activity baru di Android Studio:
- LoginActivity (akan menjadi launcher activity)
- RegisterActivity (dibuka dari link "Belum punya akun? Daftar")
Cara: Klik kanan pada package → New → Activity → Empty Activity. Beri nama LoginActivity dan RegisterActivity.
Jangan lupa daftarkan activity di AndroidManifest.xml (biasanya otomatis).
Langkah 2: Desain Layout LoginActivity
Buka activity_login.xml. Gunakan LinearLayout vertikal dengan padding. Tambahkan EditText untuk email, password, dan tombol login. Juga link ke register.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center_horizontal">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_bank_logo"
android:layout_marginBottom="32dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Masuk ke Akun Anda"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:passwordToggleEnabled="true"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Login"
android:textAllCaps="false"
android:backgroundTint="#3b7dbd"/>
<TextView
android:id="@+id/tvRegisterLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Belum punya akun? Daftar"
android:textColor="#3b7dbd"
android:padding="16dp"
android:layout_marginTop="8dp"/>
</LinearLayout>
Kita menggunakan Material Components untuk TextInputLayout. Pastikan di build.gradle sudah ada dependency com.google.android.material:material:1.9.0.
Langkah 3: Desain Layout RegisterActivity
Buat activity_register.xml serupa, tambahkan EditText untuk nama, email, password, konfirmasi password.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Daftar Akun Baru"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginBottom="32dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nama Lengkap"
android:inputType="textPersonName"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:passwordToggleEnabled="true"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:passwordToggleEnabled="true"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etConfirmPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Konfirmasi Password"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnRegister"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Daftar"
android:textAllCaps="false"
android:backgroundTint="#28a745"/>
<TextView
android:id="@+id/tvLoginLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sudah punya akun? Login"
android:textColor="#3b7dbd"
android:padding="16dp"
android:layout_marginTop="8dp"/>
</LinearLayout>
Langkah 4: Menambahkan Model Class untuk Request & Response
Buat class untuk request login (bisa pakai @Field), tapi kita akan buat POJO untuk response. Buat LoginResponse.java:
package com.example.banktabunganapp;
import com.google.gson.annotations.SerializedName;
public class LoginResponse {
@SerializedName("message")
private String message;
@SerializedName("token")
private String token;
@SerializedName("user")
private User user;
public static class User {
@SerializedName("id")
private int id;
@SerializedName("name")
private String name;
@SerializedName("email")
private String email;
@SerializedName("role")
private String role;
// getters
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getRole() { return role; }
}
// getters
public String getMessage() { return message; }
public String getToken() { return token; }
public User getUser() { return user; }
}
Untuk register, response bisa mirip (token langsung dikasih). Buat RegisterResponse.java (sama dengan LoginResponse).
Langkah 5: Menambahkan Endpoint di ApiService
Tambahkan method di ApiService.java:
@FormUrlEncoded
@POST("login")
Call<LoginResponse> login(@Field("email") String email, @Field("password") String password);
@FormUrlEncoded
@POST("register")
Call<LoginResponse> register(@Field("name") String name, @Field("email") String email, @Field("password") String password);
Perhatikan: endpoint register di Laravel (tutorial #3) mengembalikan token juga.
Langkah 6: Membuat SessionManager untuk SharedPreferences
Buat class SessionManager.java untuk menyimpan dan mengambil token serta data user:
package com.example.banktabunganapp;
import android.content.Context;
import android.content.SharedPreferences;
public class SessionManager {
private static final String PREF_NAME = "BankAppPref";
private static final String KEY_TOKEN = "token";
private static final String KEY_USER_ID = "user_id";
private static final String KEY_USER_NAME = "user_name";
private static final String KEY_USER_EMAIL = "user_email";
private static final String KEY_USER_ROLE = "user_role";
private SharedPreferences pref;
private SharedPreferences.Editor editor;
private Context context;
public SessionManager(Context context) {
this.context = context;
pref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
editor = pref.edit();
}
public void saveAuthToken(String token) {
editor.putString(KEY_TOKEN, token);
editor.commit();
}
public void saveUser(LoginResponse.User user) {
editor.putInt(KEY_USER_ID, user.getId());
editor.putString(KEY_USER_NAME, user.getName());
editor.putString(KEY_USER_EMAIL, user.getEmail());
editor.putString(KEY_USER_ROLE, user.getRole());
editor.commit();
}
public String getToken() {
return pref.getString(KEY_TOKEN, null);
}
public String getUserRole() {
return pref.getString(KEY_USER_ROLE, null);
}
public boolean isLoggedIn() {
return getToken() != null;
}
public void logout() {
editor.clear();
editor.commit();
}
}
Langkah 7: Implementasi LoginActivity
Buka LoginActivity.java. Isi dengan kode berikut:
package com.example.banktabunganapp;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class LoginActivity extends AppCompatActivity {
private EditText etEmail, etPassword;
private Button btnLogin;
private TextView tvRegisterLink;
private SessionManager sessionManager;
private ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
sessionManager = new SessionManager(this);
apiService = ApiClient.getService();
// Cek apakah sudah login? Jika ya, langsung ke dashboard sesuai role
if (sessionManager.isLoggedIn()) {
navigateToDashboard();
finish();
}
etEmail = findViewById(R.id.etEmail);
etPassword = findViewById(R.id.etPassword);
btnLogin = findViewById(R.id.btnLogin);
tvRegisterLink = findViewById(R.id.tvRegisterLink);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loginUser();
}
});
tvRegisterLink.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(LoginActivity.this, RegisterActivity.class));
}
});
}
private void loginUser() {
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
// Validasi
if (TextUtils.isEmpty(email)) {
etEmail.setError("Email tidak boleh kosong");
return;
}
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
etEmail.setError("Email tidak valid");
return;
}
if (TextUtils.isEmpty(password)) {
etPassword.setError("Password tidak boleh kosong");
return;
}
if (password.length() < 6) {
etPassword.setError("Password minimal 6 karakter");
return;
}
// Tampilkan loading (opsional)
btnLogin.setEnabled(false);
btnLogin.setText("Loading...");
Call call = apiService.login(email, password);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
btnLogin.setEnabled(true);
btnLogin.setText("Login");
if (response.isSuccessful()) {
LoginResponse loginResponse = response.body();
String token = loginResponse.getToken();
LoginResponse.User user = loginResponse.getUser();
// Simpan token dan user
sessionManager.saveAuthToken(token);
sessionManager.saveUser(user);
Toast.makeText(LoginActivity.this, "Login berhasil!", Toast.LENGTH_SHORT).show();
navigateToDashboard();
} else {
// Error dari server (email salah, dll)
try {
String errorMsg = response.errorBody().string();
Toast.makeText(LoginActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(LoginActivity.this, "Login gagal", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
btnLogin.setEnabled(true);
btnLogin.setText("Login");
Toast.makeText(LoginActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void navigateToDashboard() {
String role = sessionManager.getUserRole();
Intent intent;
if ("admin".equalsIgnoreCase(role)) {
intent = new Intent(LoginActivity.this, AdminDashboardActivity.class);
} else if ("petugas".equalsIgnoreCase(role)) {
intent = new Intent(LoginActivity.this, PetugasDashboardActivity.class);
} else {
intent = new Intent(LoginActivity.this, DashboardActivity.class); // nasabah
}
startActivity(intent);
finish();
}
}
Langkah 8: Implementasi RegisterActivity
Buat RegisterActivity.java:
package com.example.banktabunganapp;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class RegisterActivity extends AppCompatActivity {
private EditText etName, etEmail, etPassword, etConfirmPassword;
private Button btnRegister;
private TextView tvLoginLink;
private ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
apiService = ApiClient.getService();
etName = findViewById(R.id.etName);
etEmail = findViewById(R.id.etEmail);
etPassword = findViewById(R.id.etPassword);
etConfirmPassword = findViewById(R.id.etConfirmPassword);
btnRegister = findViewById(R.id.btnRegister);
tvLoginLink = findViewById(R.id.tvLoginLink);
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
registerUser();
}
});
tvLoginLink.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish(); // kembali ke login
}
});
}
private void registerUser() {
String name = etName.getText().toString().trim();
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
String confirmPassword = etConfirmPassword.getText().toString().trim();
// Validasi
if (TextUtils.isEmpty(name)) {
etName.setError("Nama tidak boleh kosong");
return;
}
if (TextUtils.isEmpty(email)) {
etEmail.setError("Email tidak boleh kosong");
return;
}
if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
etEmail.setError("Email tidak valid");
return;
}
if (TextUtils.isEmpty(password)) {
etPassword.setError("Password tidak boleh kosong");
return;
}
if (password.length() < 6) {
etPassword.setError("Password minimal 6 karakter");
return;
}
if (!password.equals(confirmPassword)) {
etConfirmPassword.setError("Password dan konfirmasi tidak cocok");
return;
}
btnRegister.setEnabled(false);
btnRegister.setText("Loading...");
Call call = apiService.register(name, email, password);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
btnRegister.setEnabled(true);
btnRegister.setText("Daftar");
if (response.isSuccessful()) {
Toast.makeText(RegisterActivity.this, "Registrasi berhasil! Silakan login.", Toast.LENGTH_LONG).show();
finish(); // kembali ke login
} else {
try {
String errorMsg = response.errorBody().string();
Toast.makeText(RegisterActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(RegisterActivity.this, "Registrasi gagal", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
btnRegister.setEnabled(true);
btnRegister.setText("Daftar");
Toast.makeText(RegisterActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
Langkah 9: Uji Coba
Pastikan server Laravel berjalan. Set LoginActivity sebagai launcher activity di AndroidManifest.xml:
<activity android:name=".LoginActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Jalankan aplikasi. Coba register akun baru, lalu login. Jika berhasil, akan masuk ke dashboard sesuai role.
Jika ada error, periksa Logcat untuk detail.
Kesimpulan
- ✅ Layout login dan register sudah didesain dengan Material Design.
- ✅ Validasi input di sisi klien mencegah data kosong/salah.
- ✅ API login dan register terintegrasi dengan Retrofit.
- ✅ Token dan data user disimpan di SharedPreferences menggunakan SessionManager.
- ✅ Navigasi ke dashboard sesuai role setelah login.
Di tutorial selanjutnya (#4: Dashboard Nasabah – Menampilkan Saldo dan Mutasi dengan RecyclerView) kita akan membuat halaman utama nasabah yang menampilkan saldo dan daftar transaksi. Sampai jumpa!
Daftar Tutorial Android (Lanjutan)
- #4: Dashboard Nasabah – RecyclerView dan Saldo
- #5: Ubah Password dan Edit Profil
- #6: Login Multi-Level (Admin, Petugas, Nasabah)