Tutorial Android #9: Dashboard Admin – Manajemen Pengguna (CRUD)

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?

😂 Joke admin: "Kenapa admin suka CRUD? Soalnya kalau nggak CRUD, user pada bandel!" 👮‍♂️

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.

💡 Catatan: Pastikan endpoint API di Laravel sudah mendukung CRUD untuk user dengan middleware isAdmin. Lihat tutorial #4 dan #14 untuk backend.

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!


Daftar Tutorial Android (Lanjutan)

😆 "Dengan CRUD, admin jadi bos sejati: bisa nambahin user baru kapan aja!" 👑
Lebih baru Lebih lama

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