Tutorial Android #9: Dashboard Admin – Manajemen Pengguna (CRUD) untuk Admin dan Petugas
Halo para admin bank! Setelah berhasil login sebagai admin, sekarang saatnya kita membangun fitur manajemen pengguna. Admin dapat melihat daftar admin dan petugas, menambah user baru, mengedit, dan menghapus. Kita akan menggunakan RecyclerView, dialog form, dan SweetAlert/AlertDialog untuk konfirmasi. Siap jadi bos user?
Yang akan kita lakukan:
- ✅ Membuat activity baru:
AdminDashboardActivity. - ✅ Mendesain layout dengan RecyclerView dan FloatingActionButton (FAB) untuk tambah user.
- ✅ Membuat model class untuk data user (UserModel) dan response dari API.
- ✅ Menambahkan endpoint di ApiService: daftar user, tambah, update, hapus.
- ✅ Membuat adapter RecyclerView dengan tampilan setiap item (nama, email, role) dan tombol edit/hapus.
- ✅ Membuat dialog form untuk tambah/edit user dengan input nama, email, password, role (admin/petugas).
- ✅ Mengimplementasikan fungsi tambah, edit, hapus dengan Retrofit.
- ✅ Menambahkan konfirmasi hapus dengan AlertDialog.
- ✅ Menampilkan loading dan menangani error (termasuk token expired).
- ✅ Menambahkan menu logout/profil (sama seperti dashboard lain).
Langkah 1: Menambahkan Dependencies (jika belum)
Pastikan di build.gradle (Module: app) sudah ada library RecyclerView, CardView, dan Material Components. Jika belum, tambahkan:
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.cardview:cardview:1.0.0' implementation 'com.google.android.material:material:1.9.0'
Langkah 2: Membuat Model Class untuk User
Buat class User.java untuk merepresentasikan data user dari API (response /api/users dari tutorial #4).
package com.example.banktabunganapp;
import com.google.gson.annotations.SerializedName;
public class User {
@SerializedName("id")
private int id;
@SerializedName("name")
private String name;
@SerializedName("email")
private String email;
@SerializedName("role")
private Role role;
@SerializedName("role_id")
private int roleId;
public static class Role {
@SerializedName("id")
private int id;
@SerializedName("name")
private String name;
public String getName() { return name; }
}
// getters
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public Role getRole() { return role; }
public int getRoleId() { return roleId; }
}
Kita juga akan butuh class untuk request create/update (karena password optional untuk update).
public class UserRequest {
private String name;
private String email;
private String password; // optional untuk update
private int role_id;
public UserRequest(String name, String email, String password, int role_id) {
this.name = name;
this.email = email;
this.password = password;
this.role_id = role_id;
}
}
Langkah 3: Menambahkan Endpoint di ApiService
Buka ApiService.java dan tambahkan method CRUD untuk user:
// GET semua users
@GET("users")
Call<List<User>> getAllUsers(@Header("Authorization") String token);
// POST create user
@POST("users")
Call<BaseResponse> createUser(@Header("Authorization") String token, @Body UserRequest userRequest);
// PUT update user
@PUT("users/{id}")
Call<BaseResponse> updateUser(@Header("Authorization") String token, @Path("id") int id, @Body UserRequest userRequest);
// DELETE user
@DELETE("users/{id}")
Call<BaseResponse> deleteUser(@Header("Authorization") String token, @Path("id") int id);
Catatan: Untuk update, password boleh kosong (tidak diubah). Di backend, kita akan menangani dengan validasi bahwa jika password tidak diisi, jangan diupdate.
Langkah 4: Membuat Layout AdminDashboardActivity
Buat file activity_admin_dashboard.xml dengan RecyclerView dan FloatingActionButton:
<?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="#e65100"
app:title="Dashboard Admin"
app:titleTextColor="#fff"
app:navigationIcon="?attr/homeAsUpIndicator"/>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewUsers"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:src="@android:drawable/ic_input_add"
app:backgroundTint="#e65100"/>
</LinearLayout>
Langkah 5: Membuat Layout Item untuk RecyclerView
Buat file item_user.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="wrap_content"
app:cardCornerRadius="8dp"
app:cardElevation="2dp"
android:layout_margin="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#666"/>
<TextView
android:id="@+id/tvRole"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:layout_marginTop="4dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="12dp">
<Button
android:id="@+id/btnEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Edit"
android:textAllCaps="false"
android:backgroundTint="#ff9800"
android:layout_marginEnd="8dp"/>
<Button
android:id="@+id/btnDelete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Hapus"
android:textAllCaps="false"
android:backgroundTint="#f44336"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
Langkah 6: Membuat Adapter UserAdapter
Buat UserAdapter.java:
package com.example.banktabunganapp;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {
private List<User> userList;
private OnUserActionListener listener;
public interface OnUserActionListener {
void onEdit(User user);
void onDelete(User user);
}
public UserAdapter(List<User> userList, OnUserActionListener listener) {
this.userList = userList;
this.listener = listener;
}
@NonNull
@Override
public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_user, parent, false);
return new UserViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
User user = userList.get(position);
holder.tvName.setText(user.getName());
holder.tvEmail.setText(user.getEmail());
holder.tvRole.setText(user.getRole().getName());
// Warna role
if ("Admin".equalsIgnoreCase(user.getRole().getName())) {
holder.tvRole.setTextColor(holder.itemView.getContext().getColor(android.R.color.holo_red_dark));
} else {
holder.tvRole.setTextColor(holder.itemView.getContext().getColor(android.R.color.holo_green_dark));
}
holder.btnEdit.setOnClickListener(v -> listener.onEdit(user));
holder.btnDelete.setOnClickListener(v -> listener.onDelete(user));
}
@Override
public int getItemCount() {
return userList.size();
}
public static class UserViewHolder extends RecyclerView.ViewHolder {
TextView tvName, tvEmail, tvRole;
Button btnEdit, btnDelete;
public UserViewHolder(@NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
tvEmail = itemView.findViewById(R.id.tvEmail);
tvRole = itemView.findViewById(R.id.tvRole);
btnEdit = itemView.findViewById(R.id.btnEdit);
btnDelete = itemView.findViewById(R.id.btnDelete);
}
}
}
Langkah 7: Membuat Dialog Form untuk Tambah/Edit User
Buat layout dialog dialog_user_form.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/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nama Lengkap"/>
</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:id="@+id/passwordLayout"
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"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etRole"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Role (admin / petugas)"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
Langkah 8: Implementasi AdminDashboardActivity
Buat AdminDashboardActivity.java:
package com.example.banktabunganapp;
import android.app.AlertDialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
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.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AdminDashboardActivity extends AppCompatActivity implements UserAdapter.OnUserActionListener {
private RecyclerView recyclerView;
private UserAdapter adapter;
private List<User> userList = new ArrayList<>();
private ProgressBar progressBar;
private FloatingActionButton fabAdd;
private SessionManager sessionManager;
private ApiService apiService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_admin_dashboard);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
sessionManager = new SessionManager(this);
apiService = ApiClient.getService();
// Cek session
if (!sessionManager.isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
progressBar = findViewById(R.id.progressBar);
recyclerView = findViewById(R.id.recyclerViewUsers);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new UserAdapter(userList, this);
recyclerView.setAdapter(adapter);
fabAdd = findViewById(R.id.fabAdd);
fabAdd.setOnClickListener(v -> showUserDialog(null));
loadUsers();
}
private void loadUsers() {
progressBar.setVisibility(View.VISIBLE);
String token = sessionManager.getToken();
String authHeader = "Bearer " + token;
Call<List<User>> call = apiService.getAllUsers(authHeader);
call.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
progressBar.setVisibility(View.GONE);
if (response.isSuccessful()) {
userList.clear();
// Filter hanya admin dan petugas
for (User user : response.body()) {
String role = user.getRole().getName();
if ("Admin".equalsIgnoreCase(role) || "Petugas".equalsIgnoreCase(role)) {
userList.add(user);
}
}
adapter.notifyDataSetChanged();
} else {
if (response.code() == 401) {
sessionManager.logout();
startActivity(new Intent(AdminDashboardActivity.this, LoginActivity.class));
finish();
} else {
Toast.makeText(AdminDashboardActivity.this, "Gagal memuat data", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
progressBar.setVisibility(View.GONE);
Toast.makeText(AdminDashboardActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onEdit(User user) {
showUserDialog(user);
}
@Override
public void onDelete(User user) {
new AlertDialog.Builder(this)
.setTitle("Konfirmasi Hapus")
.setMessage("Yakin ingin menghapus user " + user.getName() + "?")
.setPositiveButton("Ya", (dialog, which) -> deleteUser(user))
.setNegativeButton("Batal", null)
.show();
}
private void deleteUser(User user) {
progressBar.setVisibility(View.VISIBLE);
String token = sessionManager.getToken();
String authHeader = "Bearer " + token;
Call<BaseResponse> call = apiService.deleteUser(authHeader, user.getId());
call.enqueue(new Callback<BaseResponse>() {
@Override
public void onResponse(Call<BaseResponse> call, Response<BaseResponse> response) {
progressBar.setVisibility(View.GONE);
if (response.isSuccessful()) {
Toast.makeText(AdminDashboardActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
loadUsers(); // refresh
} else {
if (response.code() == 401) {
sessionManager.logout();
startActivity(new Intent(AdminDashboardActivity.this, LoginActivity.class));
finish();
} else {
Toast.makeText(AdminDashboardActivity.this, "Gagal menghapus", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onFailure(Call<BaseResponse> call, Throwable t) {
progressBar.setVisibility(View.GONE);
Toast.makeText(AdminDashboardActivity.this, "Network error", Toast.LENGTH_SHORT).show();
}
});
}
private void showUserDialog(User existingUser) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(existingUser == null ? "Tambah User" : "Edit User");
View dialogView = getLayoutInflater().inflate(R.layout.dialog_user_form, null);
builder.setView(dialogView);
TextInputEditText etName = dialogView.findViewById(R.id.etName);
TextInputEditText etEmail = dialogView.findViewById(R.id.etEmail);
TextInputEditText etPassword = dialogView.findViewById(R.id.etPassword);
TextInputEditText etRole = dialogView.findViewById(R.id.etRole);
if (existingUser != null) {
etName.setText(existingUser.getName());
etEmail.setText(existingUser.getEmail());
etRole.setText(existingUser.getRole().getName());
// Password tidak diisi (optional)
etPassword.setHint("Kosongkan jika tidak diubah");
}
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 name = etName.getText().toString().trim();
String email = etEmail.getText().toString().trim();
String password = etPassword.getText().toString().trim();
String roleStr = etRole.getText().toString().trim();
// Validasi
if (TextUtils.isEmpty(name)) {
etName.setError("Nama harus diisi");
return;
}
if (TextUtils.isEmpty(email)) {
etEmail.setError("Email harus diisi");
return;
}
if (existingUser == null && TextUtils.isEmpty(password)) {
etPassword.setError("Password harus diisi untuk user baru");
return;
}
if (TextUtils.isEmpty(roleStr)) {
etRole.setError("Role harus diisi (admin/petugas)");
return;
}
int roleId;
if ("admin".equalsIgnoreCase(roleStr)) {
roleId = 1;
} else if ("petugas".equalsIgnoreCase(roleStr)) {
roleId = 2;
} else {
etRole.setError("Role harus admin atau petugas");
return;
}
// Siapkan request
UserRequest request = new UserRequest(name, email, password, roleId);
String token = sessionManager.getToken();
String authHeader = "Bearer " + token;
progressBar.setVisibility(View.VISIBLE);
Call call;
if (existingUser == null) {
call = apiService.createUser(authHeader, request);
} else {
call = apiService.updateUser(authHeader, existingUser.getId(), request);
}
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
progressBar.setVisibility(View.GONE);
if (response.isSuccessful()) {
Toast.makeText(AdminDashboardActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
dialog.dismiss();
loadUsers();
} else {
if (response.code() == 401) {
sessionManager.logout();
startActivity(new Intent(AdminDashboardActivity.this, LoginActivity.class));
finish();
} else {
try {
String errorMsg = response.errorBody().string();
Toast.makeText(AdminDashboardActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(AdminDashboardActivity.this, "Operasi gagal", Toast.LENGTH_SHORT).show();
}
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
progressBar.setVisibility(View.GONE);
Toast.makeText(AdminDashboardActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
});
});
dialog.show();
}
@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(this, ProfileActivity.class));
return true;
} else if (item.getItemId() == R.id.action_logout) {
showLogoutConfirmation();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showLogoutConfirmation() {
new AlertDialog.Builder(this)
.setTitle("Konfirmasi Logout")
.setMessage("Apakah Anda yakin ingin keluar?")
.setPositiveButton("Ya", (dialog, which) -> {
sessionManager.logout();
startActivity(new Intent(this, LoginActivity.class));
finish();
})
.setNegativeButton("Batal", null)
.show();
}
@Override
public boolean onSupportNavigateUp() {
finish();
return true;
}
}
Langkah 9: Uji Coba
Login sebagai admin. Pastikan daftar admin/petugas muncul. Coba tambah user baru (admin atau petugas). Coba edit user. Coba hapus user. Perhatikan bahwa tidak boleh menghapus diri sendiri (backend seharusnya mencegah). Jika ada error, lihat Logcat.
Kesimpulan
- ✅ Admin dapat melihat daftar admin/petugas dengan RecyclerView.
- ✅ Menambah user baru melalui dialog dengan validasi.
- ✅ Mengedit user (password optional) dan menghapus dengan konfirmasi.
- ✅ Integrasi API berjalan dengan Retrofit.
- ✅ Penanganan error dan token expired sudah diterapkan.
Di tutorial terakhir (#10: Laporan Transaksi dengan Filter dan Ekspor PDF) kita akan membuat fitur laporan untuk admin. Sampai jumpa!