Tutorial Android #5: Ubah Password dan Edit Profil untuk Semua Level Pengguna

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? 

😂 Joke profil: "Kenapa foto profil penting? Biar kalau lupa password, teman-teman masih inget wajah kita!" 📸

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

  1. Login sebagai nasabah, petugas, atau admin.
  2. Buka halaman profil (dari menu).
  3. Pastikan data profil tampil sesuai role (untuk nasabah tampil rekening dan saldo, untuk lainnya tidak).
  4. Klik Edit Profil, ubah nama/email, simpan. Muncul notifikasi sukses dan data berubah.
  5. Klik Ubah Password, isi dengan benar, simpan. Muncul notifikasi sukses.
  6. Coba dengan data salah (password lama salah, konfirmasi tidak cocok) → muncul error.
💡 Catatan: Pastikan di backend endpoint /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
😆 "Dengan fitur ini, pengguna bisa ganti nama jadi 'Si Kaya Raya' kalau saldo sudah banyak!" 💰
Lebih baru Lebih lama

نموذج الاتصال