Tutorial Android #8: Dashboard Petugas – Fitur Pencarian dan Lihat Mutasi Nasabah
Halo para detektif keuangan! Setelah bisa melakukan transaksi, sekarang petugas juga perlu melihat mutasi (riwayat transaksi) nasabah. Kita akan membuat fitur pencarian berdasarkan ID rekening, menampilkan detail rekening, dan daftar mutasi dalam RecyclerView. Siap jadi detektif?
Apa yang akan kita lakukan?
- ✅ Membuat activity baru:
CariMutasiActivity. - ✅ Desain layout dengan form input ID rekening, tombol cari, dan area hasil.
- ✅ Menampilkan info rekening (nama, nomor rekening, saldo) dalam CardView.
- ✅ Menampilkan daftar mutasi dalam RecyclerView (bisa pakai adapter yang sama dari tutorial #4).
- ✅ Menambahkan endpoint di ApiService untuk mengambil mutasi berdasarkan account_id.
- ✅ Memanggil API dengan Retrofit, menampilkan loading, dan menangani error.
- ✅ Menambahkan menu/button di PetugasDashboard untuk menuju ke activity ini.
- ✅ Menangani kasus data tidak ditemukan.
Langkah 1: Membuat Activity dan Layout
Buat activity baru: CariMutasiActivity.java. Layoutnya activity_cari_mutasi.xml:
<?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="#0288d1"
app:title="Cari Mutasi Nasabah"
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">
<!-- Form Pencarian -->
<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="Cari Berdasarkan ID Rekening"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<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/etAccountId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Masukkan ID Rekening"
android:inputType="number"/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Cari"
android:textAllCaps="false"
android:backgroundTint="#0288d1"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- ProgressBar -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
<!-- Info Rekening (akan muncul setelah pencarian) -->
<androidx.cardview.widget.CardView
android:id="@+id/cardInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
android:layout_marginBottom="16dp"
android:visibility="gone">
<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 Rekening"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="12dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nama Nasabah:"
android:textColor="#666"/>
<TextView
android:id="@+id/tvNama"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No. Rekening:"
android:textColor="#666"/>
<TextView
android:id="@+id/tvNoRekening"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Saldo:"
android:textColor="#666"/>
<TextView
android:id="@+id/tvSaldo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:textSize="16sp"
android:textColor="#2e7d32"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Daftar Mutasi -->
<TextView
android:id="@+id/tvMutasiLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Riwayat Transaksi"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewMutasi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
android:visibility="gone"/>
<TextView
android:id="@+id/tvEmpty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Tidak ada data mutasi"
android:textAlignment="center"
android:padding="32dp"
android:visibility="gone"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
Langkah 2: Model untuk Response Mutasi Petugas
Kita akan gunakan model yang mirip dengan tutorial #4. Buat class MutasiPetugasResponse.java (atau gunakan MutasiResponse yang sudah ada). Kita asumsikan response dari endpoint /api/mutasi/{account_id} adalah:
{
"nasabah": "Budi",
"no_rekening": "REK123",
"saldo": 500000,
"mutasi": [
{
"tanggal": "01-03-2025 10:30",
"jenis": "Setor",
"jumlah": 50000,
"keterangan": "Setor tunai",
"petugas": "Andi"
}
]
}
Buat class:
package com.example.banktabunganapp;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class MutasiPetugasResponse {
@SerializedName("nasabah")
private String nasabah;
@SerializedName("no_rekening")
private String noRekening;
@SerializedName("saldo")
private double saldo;
@SerializedName("mutasi")
private List<TransaksiItem> mutasi;
public static class TransaksiItem {
@SerializedName("tanggal")
private String tanggal;
@SerializedName("jenis")
private String jenis;
@SerializedName("jumlah")
private double jumlah;
@SerializedName("keterangan")
private String keterangan;
@SerializedName("petugas")
private String petugas;
// getters
public String getTanggal() { return tanggal; }
public String getJenis() { return jenis; }
public double getJumlah() { return jumlah; }
public String getKeterangan() { return keterangan; }
public String getPetugas() { return petugas; }
}
// getters
public String getNasabah() { return nasabah; }
public String getNoRekening() { return noRekening; }
public double getSaldo() { return saldo; }
public List<TransaksiItem> getMutasi() { return mutasi; }
}
Langkah 3: Menambahkan Endpoint di ApiService
Buka ApiService.java dan tambahkan method:
@GET("mutasi/{account_id}")
Call<MutasiPetugasResponse> getMutasiByAccountId(
@Header("Authorization") String token,
@Path("account_id") int accountId
);
Langkah 4: Membuat Adapter untuk Mutasi (jika belum ada)
Kita bisa gunakan adapter yang sama dengan tutorial #4, yaitu TransaksiAdapter. Pastikan adapter tersebut bisa menerima list MutasiPetugasResponse.TransaksiItem. Jika belum, sesuaikan.
Contoh adapter (salin dari tutorial #4):
public class TransaksiAdapter extends RecyclerView.Adapter<TransaksiAdapter.ViewHolder> {
private List<MutasiPetugasResponse.TransaksiItem> transaksiList;
public TransaksiAdapter(List<MutasiPetugasResponse.TransaksiItem> transaksiList) {
this.transaksiList = transaksiList;
}
// ... onCreateViewHolder, onBindViewHolder, dll (sama seperti tutorial #4)
}
Langkah 5: Implementasi CariMutasiActivity
Buka CariMutasiActivity.java:
package com.example.banktabunganapp;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputEditText;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Locale;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class CariMutasiActivity extends AppCompatActivity {
private TextInputEditText etAccountId;
private Button btnSearch;
private ProgressBar progressBar;
private CardView cardInfo;
private TextView tvNama, tvNoRekening, tvSaldo;
private RecyclerView recyclerView;
private TransaksiAdapter adapter;
private TextView tvMutasiLabel, tvEmpty;
private SessionManager sessionManager;
private ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cari_mutasi);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
sessionManager = new SessionManager(this);
apiService = ApiClient.getService();
etAccountId = findViewById(R.id.etAccountId);
btnSearch = findViewById(R.id.btnSearch);
progressBar = findViewById(R.id.progressBar);
cardInfo = findViewById(R.id.cardInfo);
tvNama = findViewById(R.id.tvNama);
tvNoRekening = findViewById(R.id.tvNoRekening);
tvSaldo = findViewById(R.id.tvSaldo);
recyclerView = findViewById(R.id.recyclerViewMutasi);
tvMutasiLabel = findViewById(R.id.tvMutasiLabel);
tvEmpty = findViewById(R.id.tvEmpty);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new TransaksiAdapter(new ArrayList<>());
recyclerView.setAdapter(adapter);
btnSearch.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
searchMutasi();
}
});
}
private void searchMutasi() {
String accountIdStr = etAccountId.getText().toString().trim();
if (TextUtils.isEmpty(accountIdStr)) {
etAccountId.setError("ID Rekening harus diisi");
return;
}
int accountId = Integer.parseInt(accountIdStr);
// Sembunyikan hasil sebelumnya
cardInfo.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
tvMutasiLabel.setVisibility(View.GONE);
tvEmpty.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
btnSearch.setEnabled(false);
String token = sessionManager.getToken();
String authHeader = "Bearer " + token;
Call call = apiService.getMutasiByAccountId(authHeader, accountId);
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
progressBar.setVisibility(View.GONE);
btnSearch.setEnabled(true);
if (response.isSuccessful()) {
MutasiPetugasResponse data = response.body();
// Tampilkan info rekening
cardInfo.setVisibility(View.VISIBLE);
tvNama.setText(data.getNasabah());
tvNoRekening.setText(data.getNoRekening());
NumberFormat formatRupiah = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
tvSaldo.setText(formatRupiah.format(data.getSaldo()));
// Tampilkan mutasi
List mutasi = data.getMutasi();
if (mutasi != null && !mutasi.isEmpty()) {
adapter = new TransaksiAdapter(mutasi);
recyclerView.setAdapter(adapter);
recyclerView.setVisibility(View.VISIBLE);
tvMutasiLabel.setVisibility(View.VISIBLE);
tvEmpty.setVisibility(View.GONE);
} else {
recyclerView.setVisibility(View.GONE);
tvMutasiLabel.setVisibility(View.VISIBLE);
tvEmpty.setVisibility(View.VISIBLE);
}
} else {
if (response.code() == 401) {
sessionManager.logout();
startActivity(new Intent(CariMutasiActivity.this, LoginActivity.class));
finish();
} else if (response.code() == 404) {
showError("Rekening tidak ditemukan");
} else {
showError("Gagal mengambil data");
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
progressBar.setVisibility(View.GONE);
btnSearch.setEnabled(true);
showError("Network error: " + t.getMessage());
}
});
}
private void showError(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
cardInfo.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
tvMutasiLabel.setVisibility(View.GONE);
tvEmpty.setVisibility(View.GONE);
}
@Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
}
Langkah 6: Menambahkan Tombol di PetugasDashboard
Buka PetugasDashboardActivity.java (dari tutorial #7). Tambahkan tombol atau menu item untuk membuka CariMutasiActivity. Misal kita tambahkan di menu (bersama Profil dan Logout) atau di layout. Kita akan tambahkan menu item baru.
Update menu_dashboard.xml menjadi:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_cari_mutasi"
android:title="Cari Mutasi"
app:showAsAction="never"/>
<item
android:id="@+id/action_profile"
android:title="Profil"
app:showAsAction="never"/>
<item
android:id="@+id/action_logout"
android:title="Logout"
app:showAsAction="never"/>
</menu>
Di PetugasDashboardActivity, override onOptionsItemSelected:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_cari_mutasi) {
startActivity(new Intent(this, CariMutasiActivity.class));
return true;
} else if (item.getItemId() == R.id.action_profile) {
startActivity(new Intent(this, ProfileActivity.class));
return true;
} else if (item.getItemId() == R.id.action_logout) {
showLogoutConfirmation();
return true;
}
return super.onOptionsItemSelected(item);
}
Langkah 7: Uji Coba
Login sebagai petugas. Buka menu "Cari Mutasi". Masukkan ID rekening yang valid (misal 1). Klik Cari. Jika berhasil, muncul info rekening dan daftar mutasi. Coba masukkan ID yang tidak valid, muncul pesan error.
/api/mutasi/{account_id} sudah dibuat dan hanya bisa diakses oleh petugas (middleware auth:sanctum dan isPetugas). Lihat tutorial Laravel #16.
Kesimpulan
- ✅ Fitur pencarian mutasi nasabah berhasil dibuat.
- ✅ Info rekening dan daftar transaksi ditampilkan dengan rapi.
- ✅ Validasi dan error handling diterapkan.
- ✅ Terintegrasi dengan API Laravel.
Di tutorial selanjutnya (#9: Dashboard Admin – Manajemen Pengguna) kita akan membuat fitur CRUD untuk admin. Sampai jumpa!
Daftar Tutorial Android (Lanjutan)
- #9: Dashboard Admin – Manajemen Pengguna (CRUD)
- #10: Laporan Transaksi dengan Filter dan Ekspor PDF