Modul 12 CRUD Firebase [PDF]

  • 0 0 0
  • Suka dengan makalah ini dan mengunduhnya? Anda bisa menerbitkan file PDF Anda sendiri secara online secara gratis dalam beberapa menit saja! Sign Up
File loading please wait...
Citation preview

Modul 12 – CRUD Firebase Realtime Database Slamet Budi Santoso



Persiapan Modul ini akan membahas penerapan CRUD pada Firebase Realtime Database. Pada modul sebelumnya sudah diberikan contoh bagaimana Android menyimpan data secara lokal pada perangkat menggunakan SQLite. SQLite yang bersifat lokal membuat data sulit diakses melalui perangkat lain atau aplikasi lain. Firebase sendiri merupakan layanan yang disediakan Google dan menyediakan beragam fitur. Selain itu, Firebase juga tersedia Paket Spark yang gratis, sehingga dapat digunakan untuk belajar bahkan untuk para developer skala kecil. Realtime Database merupakan salah satu fitur dengan kuota gratis yang cukup besar, hingga 1 GB data tersimpan dan 100 koneksi simultan. Sehingga, kita dapat membuat aplikasi dengan database online yang memungkinkan diakses dari mana saja (dengan akses internet tentunya) dan dari berbagai platform aplikasi. Untuk dapat menambahkan Firebase ke project Android, beberapa prasyarat berikut perlu diperhatikan: • • •



Gunakan Android Studio versi terbaru (versi 3.x) Target aplikasi yang dibuat adalah API Level 16 (Jelly Bean) atau yang lebih tinggi Menggunakan Gradle 4.1 atau yang lebih baru



Modul ini menggunakan studi kasus Aplikasi Inventori Sederhana untuk memudahkan pemahaman tentang tahapan pembuatan aplikasi. Berikut persiapan yang perlu dilakukan terlebih dahulu.



Menambahkan Firebase 1.



Buat project baru, pilih Basic Activity, beri nama FirebaseInventori dan minimum SDK adalah Jelly Bean. Tunggu hingga project selesai di-built.



2.



Kemudian pilih Tools › Firebase.



3.



Pada kolom Firebase Assistant di sisi kanan, ditampilkan seluruh layanan Firebase yang dapat digunakan. Gulung hingga menjumpai fitur Realtime Database dan klik tautan “Save and retrieve data”.



4.



Berikutnya akan ditunjukkan langkah-langkah yang perlu dilakukan untuk memanfaatkan Firebase Realtime Database. Lakukan sesuai urutannya.



5.



Pertama, menghubungkan project dengan Firebase. Klik tombol Connect to Firebase. Browser akan menampilkan halaman Firebase, login dengan menggunakan akun Gmail jika belum. Selanjutnya klik tombol Buat project.



Google Analytics digunakan apabila anda ingin memanfaatkan fitur-fitur yang disediakan untuk pengembangan aplikasi. Karena aplikasi contoh ini bersifat sederhana maka fitur ini dimatikan. Klik Buat project untuk melanjutkan. Tunggu hingga Firebase selasi menyiapkan project anda. Klik Lanjutkan jika sudah tampil informasi seperti gambar berikut.



6.



Kemudian Hubungkan project di Android Studio dengan Firebase.



7.



Setelah koneksi berhasil, kembali ke Android Studio untuk melanjutkan langkah berikutnya, klik Add the Realtime Database to your app.



8.



Klik Accept Changes agar Android Studio menambahkan plugin dan pustaka yang diperlukan. Tunggu hingga Gradle selesai melakukan sinkronisasi.



9.



Setelah proses build selesai, selanjutnya adalah langkah ke-3, Configure Database Rules. Bagian ini harus dilakukan dengan cara masuk ke konsol Firebase melalui browser. Kemudian pilih Project Firebase yang akan dikonfigurasi. 10. Pilih Database dan gulung hingga bertemu dengan Realtime Database. Klik Buat database.



11.



Pilih Mulai dalam mode pengujian dan klik Aktifkan. Mode pengujian ini untuk mempermudah penyiapan database, tidak dianjurkan untuk produksi.



12. Sampai proses ini Realtime Database sudah siap menerima data. Biarkan jendela konsol Firebase tetap terbuka, terutama saat anda melakukan proses CRUD. Realtime Database akan secara otomatis diperbarui saat ada perubahan-perubahan pada data di dalamnya.



Menambahkan Aset Gambar Tambahkan juga beberapa aset yang sudah disediakan pada bahan_modul12.zip. Ekstrak dan salin ke dalam folder drawable seperti pada materi sebelumnya.



Buat Ikon Peluncur Jika dikehendaki, anda dapat membuat ikon peluncur seperti dijelaskan pada Modul 11.



Mengatur Resource colors.xml Warna-warna disesuaikan dengan ikon dan standar Firebase.



#FFCA28 #FFA000 #F57C00 #039BE5 #2C384A #FF8A65 #ECEFF1



Membuat Model Data Buatlah file Java Class baru dan beri nama Barang.java. File ini merupakan model atau struktur data barang beserta constuctor dan getter/setter-nya.



Edit skripnya hingga seperti berikut. import java.io.Serializable; public class Barang implements Serializable { private String nama; private String merk; private String harga; private String key; public Barang() { } public Barang(String nama, String merk, String harga) { this.nama = nama; this.merk = merk; this.harga = harga; } public String getNama() { return nama; } public void setNama(String nama) { this.nama = nama; } public String getMerk() { return merk; } public void setMerk(String merk) { this.merk = merk; } public String getHarga() { return harga; } public void setHarga(String harga) { this.harga = harga; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } }



Membuat Fitur Create Komponen pertama dalam aplikasi ini adalah fungsi Create yang digunakan untuk membuat atau memasukkan data ke dalam Realtime Database.



Layout: tambah_edit.xml Buat sebuah Layout Resource File dan beri nama tambah_edit.xml dan edit skripnya seperti berikut:











CreateActivity.java Berikutnya membuat activity untuk memasukkan data ke Realtime Database. Buatlah sebuah Java Class baru yang meng-extends AppCompatActivity.



Kemudian daftarkan CreateActivity ke dalam AndroidManifest dengan cara posisikan kursor pada nama kelas kemudian alt+enter dan pilih Add activity to manifest. Perhatikan gambar berikut.



Menambahkan activity ke dalam file AndroidManifest.xml juga dapat dilakukan secara manual, mengetikkan nama activity-nya. Kemudian lengkapi skrip CreateActivity.java seperti berikut. import import import import import import import import import import import import import



android.content.Context; android.os.Bundle; android.view.View; android.view.inputmethod.InputMethodManager; android.widget.Button; android.widget.EditText; android.widget.TextView; androidx.appcompat.app.AppCompatActivity; com.google.android.gms.tasks.OnSuccessListener; com.google.android.material.snackbar.Snackbar; com.google.firebase.database.DatabaseReference; com.google.firebase.database.FirebaseDatabase; static android.text.TextUtils.isEmpty;



public class CreateActivity extends AppCompatActivity { private DatabaseReference database; private EditText etNama; private EditText etMerk; private EditText etHarga; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tambah_edit); setTitle("Tambah Data"); etNama = findViewById(R.id.etNama); etMerk = findViewById(R.id.etMerk); etHarga = findViewById(R.id.etHarga); Button btnSimpan = findViewById(R.id.btnSimpan); btnSimpan.setText("Simpan");



//ambil referensi ke Firebase database = FirebaseDatabase.getInstance().getReference(); //fungsikan btnSimpan btnSimpan.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if( !isEmpty(etNama.getText().toString()) && !isEmpty(etMerk.getText().toString()) && !isEmpty(etHarga.getText().toString()) ){ //simpanBarang String brgNama = etNama.getText().toString(); String brgMerk = etMerk.getText().toString(); String brgHarga = etHarga.getText().toString(); Barang barang = new Barang(brgNama, brgMerk, brgHarga); database.child("barang") .push() .setValue(barang) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { etHarga.setText(""); etMerk.setText(""); etNama.setText(""); Snackbar.make(findViewById(R.id.btnSimpan), "Data berhasil ditambahkan.", Snackbar.LENGTH_SHORT).show(); } }); } else { Snackbar.make(findViewById(R.id.btnSimpan), "Data barang harus diisi semua.", Snackbar.LENGTH_SHORT).show(); } InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(etNama.getWindowToken(),0); } }); } }



Edit: MainActivity.java Kita manfaatkan Floating Action Button atau FAB untuk memanggil CreateActivity. Edit skrip pada bagian ini FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //panggil CreateActivity ketika FAB di-tap Intent intent = new Intent(MainActivity.this, CreateActivity.class); startActivity(intent); } });



Uji Coba Run project ke AVD atau ke perangkat, tap FAB dan isikan data pada form kemudian tap button Simpan.



Periksa juga di konsol Firebase dan perhatikan tambahan data yang baru saja dibuat.



AndroidManifest.xml Jika dijumpai error atau data tidak dapat ditambahkan, kemungkinan penyebabnya adalah ijin akses internet perlu diberikan pada aplikasi. Tambahkan ijin akses internet pada file AndroidManifest.xml berikut ini dan letakkan tepat di atas tag .







...



Membuat Fitur Retrieve Fitur ini akan menempati halaman pertama yang menggunakan layout activity_main.xml. Dalam layout ini akan memuat RecyclerView untuk menampung daftar barang dan efek saat belum ada data pada Realtime Database.



Layout: baris_barang.xml Buat sebuah Layout Resource File dan beri nama baris_barang.xml. Kemudian edit skripnya seperti berikut.







Edit: content_main.xml Edit skripnya seperti berikut untuk dapat memuat RecyclerView.







Adapter RecyclerView: AdapterRvBarang.java Berikutnya, buatlah sebuah Java Class untuk mengatur penampilan data ke RecyclerView. Beri nama AdapterRvBarang.java dan meng-extends RecyclerView.Adapter.



Lengkapi skripnya seperti berikut. import import import import import import import import



android.content.Context; android.view.LayoutInflater; android.view.View; android.view.ViewGroup; android.widget.TextView; androidx.annotation.NonNull; androidx.recyclerview.widget.RecyclerView; java.util.ArrayList;



public class AdapterRvBarang extends RecyclerView.Adapter { private ArrayList daftarBarang; private Context context; private FirebaseDataListener listener; public AdapterRvBarang(ArrayList daftarBarang, Context context) { this.daftarBarang = daftarBarang; this.context = context; listener = (MainActivity) context; } @NonNull @Override public AdapterRvBarang.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.baris_barang, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull AdapterRvBarang.ViewHolder holder, int position) { //menampilkan data pada view final String namaBrg = daftarBarang.get(position).getNama(); //setOnClickListener pada tiap data holder.tvNama.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //skrip untuk menampilkan detil data barang di bawah ini } }); //setOnLongClickListener pada tiap data holder.tvNama.setOnLongClickListener(new View.OnLongClickListener(){ @Override public boolean onLongClick(View v) {



//skrip untuk menampilkan pilihan Edit-Delete di bawah sini return false; } }); holder.tvNama.setText(namaBrg); } @Override public int getItemCount() { return daftarBarang.size(); } public class ViewHolder extends RecyclerView.ViewHolder { TextView tvNama; public ViewHolder(@NonNull View itemView) { super(itemView); tvNama = itemView.findViewById(R.id.tvNama); } } public interface FirebaseDataListener{ void onDeleteData(Barang barang, int position); } }



Edit: MainActivity.java MainActivity adalah activity pertama yang ditampilkan saat aplikasi dijalankan. Di sini, MainActivity akan menampilkan daftar barang yang di-retrieve dari Firebase Realtime Database. import import import import import import import import import import import import import import import import import



android.content.Intent; android.os.Bundle; android.view.Menu; android.view.MenuItem; android.view.View; androidx.annotation.NonNull; androidx.appcompat.app.AppCompatActivity; androidx.appcompat.widget.Toolbar; androidx.recyclerview.widget.LinearLayoutManager; androidx.recyclerview.widget.RecyclerView; com.google.android.material.floatingactionbutton.FloatingActionButton; com.google.firebase.database.DataSnapshot; com.google.firebase.database.DatabaseError; com.google.firebase.database.DatabaseReference; com.google.firebase.database.FirebaseDatabase; com.google.firebase.database.ValueEventListener; java.util.ArrayList;



public class MainActivity extends AppCompatActivity { private DatabaseReference database; private RecyclerView recyclerView; private RecyclerView.Adapter adapter; private RecyclerView.LayoutManager layoutManager; private ArrayList daftarBarang; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //panggil CreateActivity ketika FAB di-tap Intent intent = new Intent(MainActivity.this, CreateActivity.class); startActivity(intent);



} }); recyclerView = findViewById(R.id.rvBarang); recyclerView.setHasFixedSize(true); layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); //inisialisasi Firebase database = FirebaseDatabase.getInstance().getReference(); //mengambil data barang database.child("barang").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { daftarBarang = new ArrayList(); for(DataSnapshot dataBarang : dataSnapshot.getChildren()){ //mapping data dari snapshot ke object Barang Barang barang = dataBarang.getValue(Barang.class); barang.setKey(dataBarang.getKey()); //menambahkan object barang ke daftarBarang daftarBarang.add(barang); } //inisialisasi adapter adapter = new AdapterRvBarang(daftarBarang, MainActivity.this); recyclerView.setAdapter(adapter); } @Override public void onCancelled(@NonNull DatabaseError databaseError) { //skrip ketika ada error, tampilkan di logcat System.out.println(databaseError.getDetails() + " " + databaseError.getMessage()); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } }



Uji Coba Run project ke AVD atau ke perangkat, jangan lupa untuk terhubung ke internet agar proses retrieve berlangsung dengan baik. Hasilnya kurang lebih seperti berikut.



Membuat Fitur Update Fitur berikutnya adalah untuk memperbarui data atau update. Cara kerja fitur ini, data barang yang akan di-edit ditampilkan pada form, di-edit seperlunya, kemudian di-simpan kembali ke Realtime Database. Layout tampilan akan menggunakan file tambah_edit.xml. Buatlah sebuah Java Class, beri nama UpdateActivity.java dan meng-extends AppCompatActivity.



Jangan lupa untuk menambahkan UpdateActivity ke AndroidManifest seperti pada CreateActivity. Lengkapi skripnya seperti berikut ini. import import import import import import import import import



android.os.Bundle; android.view.View; android.widget.Button; android.widget.EditText; androidx.appcompat.app.AppCompatActivity; com.google.android.gms.tasks.OnSuccessListener; com.google.android.material.snackbar.Snackbar; com.google.firebase.database.DatabaseReference; com.google.firebase.database.FirebaseDatabase;



public class UpdateActivity extends AppCompatActivity { private DatabaseReference database; private EditText etNama; private EditText etMerk; private EditText etHarga; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tambah_edit); getSupportActionBar().setTitle("Edit Data"); etNama = findViewById(R.id.etNama); etMerk = findViewById(R.id.etMerk); etHarga = findViewById(R.id.etHarga); Button btnSimpan = findViewById(R.id.btnSimpan); btnSimpan.setText("Perbarui"); //ambil referensi ke Firebase database = FirebaseDatabase.getInstance().getReference(); final Barang barang = (Barang)getIntent().getSerializableExtra("data"); if( barang != null ){ etNama.setText(barang.getNama());



etMerk.setText(barang.getMerk()); etHarga.setText(barang.getHarga()); btnSimpan.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { barang.setNama(etNama.getText().toString()); barang.setMerk(etMerk.getText().toString()); barang.setHarga(etHarga.getText().toString()); //proses update barang database.child("barang") .child(barang.getKey()) .setValue(barang) .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { Snackbar.make(findViewById(R.id.btnSimpan), "Data berhasil diperbarui", Snackbar.LENGTH_LONG) .show(); } }); } }); } } }



Edit: AdapterRvBarang.java Edit pada bagian berikut ini. //setOnLongClickListener pada tiap data holder.tvNama.setOnLongClickListener(new View.OnLongClickListener(){ @Override public boolean onLongClick(View v) { //skrip untuk menampilkan pilihan Edit-Delete di bawah sini CharSequence[] opsi = new CharSequence[]{"Edit", "Hapus"}; final AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle("Pilih Aksi"); builder.setItems(opsi, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if( which == 0 ){ //tampilkan UpdateActivity Intent intent = new Intent(context, UpdateActivity.class); intent.putExtra("data", daftarBarang.get(position)); context.startActivity(intent); } else { //skrip delete di bawah sini } } }); builder.show(); return true; } });



Uji Coba Jalankan kembali project ke AVD atau ke perangkat android yang digunakan. Hasilnya kurang lebih seperti berikut ini. Cek juga perubahan pada Realtime Database melalui konsol Firebase.



Membuat Fitur Delete Selanjutnya adalah fitur untuk menghapus data dari Realtime Database. Edit beberapa file berikut.



Edit: AdapterRvBarang.java Tambahkan 2 (dua) baris perintah di bawah teks //skrip delete di bawah sini seperti ditunjukkan pada potongan skrip berikut. //skrip delete di bawah sini dialog.dismiss(); listener.onDeleteData(daftarBarang.get(position), position);



Edit: MainActivity.java Tambahkan implements AdapterRvBarang.FirebaseDataListener pada class MainActivity. Dan buat method onDeleteData. Perhatikan perubahan (yang berwarna merah) pada skrip MainActivity berikut. public class MainActivity extends AppCompatActivity implements AdapterRvBarang.FirebaseDataListener { private DatabaseReference database; private RecyclerView recyclerView; private RecyclerView.Adapter adapter; private RecyclerView.LayoutManager layoutManager; private ArrayList daftarBarang; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //panggil CreateActivity ketika FAB di-tap Intent intent = new Intent(MainActivity.this, CreateActivity.class); startActivity(intent); } }); recyclerView = findViewById(R.id.rvBarang); recyclerView.setHasFixedSize(true); layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); //inisialisasi Firebase database = FirebaseDatabase.getInstance().getReference(); //mengambil data barang database.child("barang").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { daftarBarang = new ArrayList(); for(DataSnapshot dataBarang : dataSnapshot.getChildren()){ //mapping data dari snapshot ke object Barang Barang barang = dataBarang.getValue(Barang.class); barang.setKey(dataBarang.getKey()); //menambahkan object barang ke daftarBarang daftarBarang.add(barang); } //inisialisasi adapter adapter = new AdapterRvBarang(daftarBarang, MainActivity.this); recyclerView.setAdapter(adapter); } @Override



public void onCancelled(@NonNull DatabaseError databaseError) { //skrip ketika ada error, tampilkan di logcat System.out.println(databaseError.getDetails() + " " + databaseError.getMessage()); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); //noinspection SimplifiableIfStatement if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } @Override public void onDeleteData(Barang barang, int position) { if( database != null ){ database.child("barang") .child(barang.getKey()) .removeValue() .addOnSuccessListener(new OnSuccessListener() { @Override public void onSuccess(Void aVoid) { Snackbar.make(recyclerView, "Data berhasil dihapus", Snackbar.LENGTH_SHORT).show(); } }); } } }



Uji Coba Run project ke AVD atau perangkat android, coba hapus dengan tap-lama (long click) pada salah satu data, kemudian pilih Hapus. Jika data terhapus, maka data tidak akan ditampilkan dan muncul snackbar di bawah layar seperti berikut.



Membuat Fitur Detail View Sebagaimana sudah dibuat, RecyclerView hanya menampilkan nama barang. Fitur berikutnya yang perlu dibuat adalah untuk menampilkan detil data barang ketika nama barang di-tap. Buat sebuah Empty Activity dan beri nama DetailActivity.



Layout: activity_detail.xml Edit skripnya seperti berikut.















DetailActivity.java Lengkapi skripnya seperti berikut. import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class DetailActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); TextView tvNama = findViewById(R.id.tvNama); TextView tvMerk = findViewById(R.id.tvMerk); TextView tvHarga = findViewById(R.id.tvHarga); Barang barang = (Barang) getIntent().getSerializableExtra("data"); if( barang != null ){ tvNama.setText(barang.getNama()); tvMerk.setText(barang.getMerk()); tvHarga.setText(barang.getHarga()); } } }



Edit: AdapterRvBarang.java Tambahkan skrip di bawah teks //skrip untuk menampilkan detil barang di bawah ini. Skrip ini berfungsi untuk mengirimkan data yang akan ditampilkan di DetailActivity. //skrip untuk menampilkan detil data barang di bawah ini Intent intent = new Intent(context, DetailActivity.class); intent.putExtra("data", daftarBarang.get(position)); context.startActivity(intent);



Uji Coba Setelah selesai di-edit, periksa kembali skripnya untuk memastikan tidak ada error. Jalankan project ke AVD atau ke perangkat untuk menguji keberhasilan skrip. Jika benar, tampilannya kurang lebih seperti berikut.



Anda dapat menghapus beberapa file yang tidak digunakan seperti file-file fragment dan layout-nya. Begitu pula folder navigation beserta isinya.



Tantangan Ada beberapa hal yang dapat ditambahkan pada aplikasi ini, misalnya: • •



Menampilkan pemisah ribuan pada harga barang (Detail View). Menambahkan pemisah ribuat saat meng-input-kan harga barang (Create/Edit).