Tutorial Android #5: Fitur Ubah Password dan Edit Profil untuk Semua Level Pengguna
Halo para pengelola akun! Setelah berhasil login dan melihat dashboard, sekarang saatnya kita menambahkan fitur pengaturan profil yang bisa digunakan oleh semua pengguna (nasabah, petugas, admin). Mereka bisa melihat data diri (nama, email), mengeditnya, dan mengubah password. Siap jadi admin akun sendiri?
Apa yang akan kita lakukan?
- ✅ Membuat activity baru:
ProfileActivity. - ✅ Desain layout untuk menampilkan informasi profil (nama, email, role, dan untuk nasabah: no rekening & saldo).
- ✅ Membuat dialog (atau activity terpisah) untuk edit profil dan ubah password.
- ✅ Menambahkan endpoint di ApiService: ambil profil, update profil, ubah password.
- ✅ Menggunakan SessionManager untuk mendapatkan token.
- ✅ Validasi input di sisi client (tidak kosong, email valid, password minimal 6, konfirmasi cocok).
- ✅ Menampilkan notifikasi sukses/gagal dengan Toast atau AlertDialog.
- ✅ Mengupdate data di SessionManager setelah edit profil (nama, email).
- ✅ Menambahkan menu/button di dashboard untuk menuju ke halaman profil.
Langkah 1: Menambahkan Model Class untuk Response Profil
Buat class ProfileResponse.java berdasarkan response dari endpoint /api/profile (yang akan kita buat di backend, mengacu pada tutorial Laravel #18):
package com.example.banktabunganapp;
import com.google.gson.annotations.SerializedName;
public class ProfileResponse {
@SerializedName("id")
private int id;
@SerializedName("name")
private String name;
@SerializedName("email")
private String email;
@SerializedName("role")
private String role;
// Untuk nasabah
@SerializedName("account_number")
private String accountNumber;
@SerializedName("balance")
private double balance;
// getters
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getRole() { return role; }
public String getAccountNumber() { return accountNumber; }
public double getBalance() { return balance; }
}
Untuk update profil, kita bisa kirim data dengan model UpdateProfileRequest.java atau langsung pakai @Field.
Langkah 2: Menambahkan Endpoint di ApiService
Buka ApiService.java dan tambahkan method berikut:
// Ambil data profil
@GET("profile")
Call<ProfileResponse> getProfile(@Header("Authorization") String token);
// Update profil (PUT)
@FormUrlEncoded
@PUT("profile")
Call<UpdateProfileResponse> updateProfile(
@Header("Authorization") String token,
@Field("name") String name,
@Field("email") String email
);
// Ubah password (POST)
@FormUrlEncoded
@POST("change-password")
Call<BaseResponse> changePassword(
@Header("Authorization") String token,
@Field("current_password") String currentPassword,
@Field("new_password") String newPassword,
@Field("new_password_confirmation") String confirmPassword
);
Buat class response sederhana untuk update dan change password (misal BaseResponse.java berisi field message):
package com.example.banktabunganapp;
import com.google.gson.annotations.SerializedName;
public class BaseResponse {
@SerializedName("message")
private String message;
public String getMessage() { return message; }
}
Untuk update profile, bisa juga pakai BaseResponse.
Langkah 3: Desain Layout ProfileActivity
Buat file activity_profile.xml dengan tampilan profil dan tombol aksi:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#f5f5f5">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#3b7dbd"
app:title="Profil Saya"
app:titleTextColor="#fff"
app:navigationIcon="?attr/homeAsUpIndicator"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
android:layout_marginBottom="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Informasi Akun"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama"
android:textColor="#666"/>
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:layout_marginBottom="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Email"
android:textColor="#666"/>
<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:layout_marginBottom="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Role"
android:textColor="#666"/>
<TextView
android:id="@+id/tvRole"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:layout_marginBottom="12dp"/>
<!-- Untuk nasabah, tampilkan rekening -->
<TextView
android:id="@+id/tvAccountNumberLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No. Rekening"
android:textColor="#666"
android:visibility="gone"/>
<TextView
android:id="@+id/tvAccountNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:layout_marginBottom="12dp"
android:visibility="gone"/>
<TextView
android:id="@+id/tvBalanceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saldo"
android:textColor="#666"
android:visibility="gone"/>
<TextView
android:id="@+id/tvBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:textColor="#2e7d32"
android:visibility="gone"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/btnEditProfile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Edit Profil"
android:textAllCaps="false"
android:backgroundTint="#3b7dbd"
android:layout_marginBottom="8dp"/>
<Button
android:id="@+id/btnChangePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ubah Password"
android:textAllCaps="false"
android:backgroundTint="#e67e22"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
Langkah 4: Membuat Dialog Edit Profil
Kita bisa menggunakan AlertDialog dengan layout kustom. Buat layout dialog_edit_profile.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<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/etEditName"
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"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etEditEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Email"
android:inputType="textEmailAddress"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
Langkah 5: Membuat Dialog Ubah Password
Layout dialog_change_password.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<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/etCurrentPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password Lama"
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="16dp"
app:passwordToggleEnabled="true"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etNewPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password Baru"
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"
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 Baru"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
Langkah 6: Implementasi ProfileActivity
Buat ProfileActivity.java:
package com.example.banktabunganapp;
import android.app.AlertDialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.textfield.TextInputEditText;
import java.text.NumberFormat;
import java.util.Locale;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ProfileActivity extends AppCompatActivity {
private TextView tvName, tvEmail, tvRole, tvAccountNumber, tvBalance, tvAccountNumberLabel, tvBalanceLabel;
private Button btnEditProfile, btnChangePassword;
private SessionManager sessionManager;
private ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
tvName = findViewById(R.id.tvName);
tvEmail = findViewById(R.id.tvEmail);
tvRole = findViewById(R.id.tvRole);
tvAccountNumber = findViewById(R.id.tvAccountNumber);
tvBalance = findViewById(R.id.tvBalance);
tvAccountNumberLabel = findViewById(R.id.tvAccountNumberLabel);
tvBalanceLabel = findViewById(R.id.tvBalanceLabel);
btnEditProfile = findViewById(R.id.btnEditProfile);
btnChangePassword = findViewById(R.id.btnChangePassword);
sessionManager = new SessionManager(this);
apiService = ApiClient.getService();
loadProfile();
btnEditProfile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showEditProfileDialog();
}
});
btnChangePassword.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showChangePasswordDialog();
}
});
}
private void loadProfile() {
String token = sessionManager.getToken();
if (token == null) {
Toast.makeText(this, "Token tidak ditemukan", Toast.LENGTH_SHORT).show();
finish();
return;
}
String authHeader = "Bearer " + token;
Call call = apiService.getProfile(authHeader);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
ProfileResponse profile = response.body();
tvName.setText(profile.getName());
tvEmail.setText(profile.getEmail());
tvRole.setText(profile.getRole());
// Tampilkan info rekening hanya jika role nasabah
if ("nasabah".equalsIgnoreCase(profile.getRole())) {
tvAccountNumberLabel.setVisibility(View.VISIBLE);
tvAccountNumber.setVisibility(View.VISIBLE);
tvBalanceLabel.setVisibility(View.VISIBLE);
tvBalance.setVisibility(View.VISIBLE);
tvAccountNumber.setText(profile.getAccountNumber());
NumberFormat formatRupiah = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
tvBalance.setText(formatRupiah.format(profile.getBalance()));
} else {
tvAccountNumberLabel.setVisibility(View.GONE);
tvAccountNumber.setVisibility(View.GONE);
tvBalanceLabel.setVisibility(View.GONE);
tvBalance.setVisibility(View.GONE);
}
} else {
Toast.makeText(ProfileActivity.this, "Gagal memuat profil", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call call, Throwable t) {
Toast.makeText(ProfileActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void showEditProfileDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Edit Profil");
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_edit_profile, null);
builder.setView(dialogView);
TextInputEditText etName = dialogView.findViewById(R.id.etEditName);
TextInputEditText etEmail = dialogView.findViewById(R.id.etEditEmail);
// Isi dengan data saat ini
etName.setText(tvName.getText().toString());
etEmail.setText(tvEmail.getText().toString());
builder.setPositiveButton("Simpan", null); // akan override
builder.setNegativeButton("Batal", null);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(dialogInterface -> {
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(v -> {
String name = etName.getText().toString().trim();
String email = etEmail.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;
}
// Lakukan update
updateProfile(name, email, dialog);
});
});
dialog.show();
}
private void updateProfile(String name, String email, AlertDialog dialog) {
String token = sessionManager.getToken();
String authHeader = "Bearer " + token;
Call call = apiService.updateProfile(authHeader, name, email);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
Toast.makeText(ProfileActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
// Update tampilan
tvName.setText(name);
tvEmail.setText(email);
// Update session jika perlu
// sessionManager.updateUser(name, email); // perlu method di SessionManager
dialog.dismiss();
} else {
try {
String errorMsg = response.errorBody().string();
Toast.makeText(ProfileActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(ProfileActivity.this, "Update gagal", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
Toast.makeText(ProfileActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
private void showChangePasswordDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Ubah Password");
LayoutInflater inflater = getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_change_password, null);
builder.setView(dialogView);
TextInputEditText etCurrent = dialogView.findViewById(R.id.etCurrentPassword);
TextInputEditText etNew = dialogView.findViewById(R.id.etNewPassword);
TextInputEditText etConfirm = dialogView.findViewById(R.id.etConfirmPassword);
builder.setPositiveButton("Simpan", null);
builder.setNegativeButton("Batal", null);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(dialogInterface -> {
Button button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(v -> {
String current = etCurrent.getText().toString().trim();
String newPass = etNew.getText().toString().trim();
String confirm = etConfirm.getText().toString().trim();
// Validasi
if (TextUtils.isEmpty(current)) {
etCurrent.setError("Password lama harus diisi");
return;
}
if (TextUtils.isEmpty(newPass)) {
etNew.setError("Password baru harus diisi");
return;
}
if (newPass.length() < 6) {
etNew.setError("Password minimal 6 karakter");
return;
}
if (!newPass.equals(confirm)) {
etConfirm.setError("Konfirmasi password tidak cocok");
return;
}
// Lakukan change password
changePassword(current, newPass, confirm, dialog);
});
});
dialog.show();
}
private void changePassword(String current, String newPass, String confirm, AlertDialog dialog) {
String token = sessionManager.getToken();
String authHeader = "Bearer " + token;
Call call = apiService.changePassword(authHeader, current, newPass, confirm);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
Toast.makeText(ProfileActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
dialog.dismiss();
// Opsional: logout otomatis
} else {
try {
String errorMsg = response.errorBody().string();
Toast.makeText(ProfileActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(ProfileActivity.this, "Gagal ubah password", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
Toast.makeText(ProfileActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
@Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
}
Langkah 7: Menambahkan Menu Profil di Dashboard
Di setiap dashboard (nasabah, petugas, admin), tambahkan tombol atau menu untuk menuju ProfileActivity. Misal di DashboardActivity (nasabah), kita bisa tambahkan item di toolbar atau tombol di layout.
Contoh sederhana: tambahkan menu di toolbar. Buat file menu menu_dashboard.xml di folder res/menu/:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_profile"
android:title="Profil"
app:showAsAction="never"/>
</menu>
Di DashboardActivity, override onCreateOptionsMenu dan onOptionsItemSelected:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_dashboard, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_profile) {
startActivity(new Intent(DashboardActivity.this, ProfileActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
Jangan lupa import yang diperlukan.
Langkah 8: Uji Coba
- Login sebagai nasabah, petugas, atau admin.
- Buka halaman profil (dari menu).
- Pastikan data profil tampil sesuai role (untuk nasabah tampil rekening dan saldo, untuk lainnya tidak).
- Klik Edit Profil, ubah nama/email, simpan. Muncul notifikasi sukses dan data berubah.
- Klik Ubah Password, isi dengan benar, simpan. Muncul notifikasi sukses.
- Coba dengan data salah (password lama salah, konfirmasi tidak cocok) → muncul error.
/api/profile (GET) dan /api/profile (PUT) serta /api/change-password (POST) sudah tersedia. Lihat tutorial Laravel #17 dan #18.
Kesimpulan
- ✅ Halaman profil berhasil dibuat dengan tampilan dinamis sesuai role.
- ✅ Edit profil dan ubah password menggunakan dialog dengan validasi.
- ✅ Integrasi API berjalan lancar dengan Retrofit.
- ✅ Notifikasi memberi feedback ke pengguna.
Di tutorial selanjutnya (#6: Login Multi-Level (Admin, Petugas, Nasabah)) kita akan memastikan navigasi dan akses sesuai role berjalan sempurna. Sampai jumpa!
Daftar Tutorial Android (Lanjutan)
- #6: Login Multi-Level (Admin, Petugas, Nasabah)
- #7: Dashboard Petugas – Transaksi Setor/Tarik
- #8: Dashboard Petugas – Lihat Mutasi Nasabah
- #9: Dashboard Admin – Manajemen Pengguna
- #10: Laporan Transaksi dengan Filter dan Ekspor PDF