Database untuk BukuSakuKu

...sebelum membaca tentang database untuk BukuSakuKu, silahkan melihat dahulu class untuk halaman depan_nya dan class untuk halaman isi_nya dengan klik disini.

Mengikuti nalar 'best practice' yang sering di kumandangkan oleh para ahli coding, maka class-class yang menyangkut database untuk BukuSakuKu ini akan saya bungkus dalam satu paket dengan nama com.vik_sintus.bukusakuku.database_nya
Menamakan paket adalah sangat tergantung struktur file masing-masing developer, di dunia java biasanya nama paket memakai cara PSV(period seperated values) atau tulisan yang di pisahkan oleh titik.

Seperti umumnya database, dia bekerja di belakang layar alias tidak berhadapan langsung dengan pengguna HP atau pengguna komputer. Yang kita lihat di layar HP adalah hanya yang indah-indah saja. Kita tak peduli mengapa yang indah-indah itu bisa muncul. Tapi para developer tentunya sangat peduli mengapa dan bagaimana suatu object tampil anggun di layar HP. Yang paling mendasar adalah komunikasi antara database dan aplikasinya, kalau komunikasinya tak benar maka segala sesuatu yang indah tak ada gunanya, karena tak akan muncul di layar HP. Itulah sebabnya mengapa saya menyimpan semua data yang menyangkut database di dalam satu paket.

Di Android, percakapan antara database dan aplikasi di lakukan oleh class yang bernama SQLiteOpenHelper.java dan sesuai namanya class ini kerjanya membantu membuka database.
Sedangkan SQLite adalah database yang di pakai di dalam semua android sistim. Android menggunakan SQLite karena ia serverless atau tak perlu server atau self contained sehingga enteng untuk alat-alat kecil seperti HP.
Mengikuti logika tersebut di atas maka class database untuk BukuSakuKu akan saya menamakan-nya DatabaseHelperNya.java yang akan merangkul class bawaan android yang bernama SQLiteOpenHelper.java
Berikut adalah isi dari DatabaseHelperNya.java
package com.vik_sintus.bukusakuku.database_nya;
/*
 * Copyright (C)2013 Vik Sintus Projects
 *
 * di larang pakai kode ini untuk kepentingan 
 * komersial tanpa ijin vik.sintus@gmail.com.
 * http://belajar-android-indonesia.blogspot.com
 *
 * Unless required by applicable law or agreed to in writing, 
 * this software is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied
 */
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelperNya extends SQLiteOpenHelper {

  private static final String NAMA_DATABASENYA = "bukusakukuDB.db";
  private static final int VERSI_DATABASENYA = 1;

  public DatabaseHelperNya(Context membuatDatabase) {
    super(membuatDatabase, NAMA_DATABASENYA, null, VERSI_DATABASENYA);
  }

  // Metode berikut akan di panggil saat membuat database
  @Override
  public void onCreate(SQLiteDatabase buatlahDatabaseNya) {
    ClassUntukTable.onCreate(buatlahDatabaseNya);
  }
 
  // Metode berikut di panggil ketika databasenya di upgrade,
  // tapi developer harus ubah angka versinya ke yang lebih tinggi yah
  // misalnya VERSI_DATABASENYA=2; dst
  @Override
  public void onUpgrade(SQLiteDatabase database_nya, int versiLama,
      int versiBaru) {
   ClassUntukTable.onUpgrade(database_nya, versiLama, versiBaru);
  }
} 

Kode di atas telah saya tulis menggunakan logika dan bahasa harian sehingga mudah untuk di pahami. Logikanya begini, deklarasikan nama class dengan menggandeng class SQLiteOpenHelper kemudian deklarasikan  NAMA_DATABASENYA dan VERSI_DATABASENYA (keduanya mirip variable) sampai disini analoginya sama seperti kita sudah punya kayu dan paku untuk membangun rumah(tapi dalam hal ini kita sedang membangun class java). Setelah alat-alatnya semua telah di siapkan maka di lanjutkan dengan  konstruksinya yang di mulai dari public DatabaseHelperNya(Context membuatDatabase) yaitu dalam konteks apa class ini di buat? tentunya kita mau membuatDatabase yang akan di kerjakan oleh metode onCreate() dan di dalam database akan ada table di ClassUntukTable.java

Masih di bawah paket com.vik_sintus.bukusakuku.database_nya buatkan class khusus untuk skema  table-nya. Tadi di DatabaseHelperNya telah di deklarasikan nama databasenya bukusakukuDB.db sekarang kita butuh sebuah table di dalam database tersebut. Oleh karena table mempunyai hubungan erat dengan database (hampir tak ada database tanpa table) maka class untuk table saya letakkan bersama class-class yang menyangkut database. Berikut adalah isi dari class ClassUntukTable.java

package com.vik_sintus.bukusakuku.database_nya;
/*
 * Copyright (C) 2013 Vik Sintus Projects
 *
 * Segala kelebihan dan kekurangan di 
 * luar tanggung jawab pembuat.
 * Di larang memakai kode ini untuk 
 * kepentingan komersial tanpa ijin.
 * Silahkan di pakai untuk kepentingan belajar.
 * vik.sintus@gmail.com
 *
 * Unless required by applicable law or agreed to in writing, 
 * this software is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 * either express or implied.
 * 
 * 
 */
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

public class ClassUntukTable {
  // table untuk database bukusakukuDB.db
   public static final String TABLE_NYA = "bukusakuku";
   public static final String KOLOM_ID = "_id";
   public static final String KOLOM_JENIS = "jenis";
   public static final String KOLOM_JUDUL = "judul";
   public static final String KOLOM_ISI = "isi";

   // membuat table dengan SQL statement_nya
   private static final String BUATLAH_TABLE_NYA = "create table " 
       + TABLE_NYA
       + "(" 
       + KOLOM_ID + " integer primary key autoincrement, " 
       + KOLOM_JENIS + " text not null, " 
       + KOLOM_JUDUL + " text not null," 
       + KOLOM_ISI
       + " text not null" 
       + ");";

   public static void onCreate(SQLiteDatabase database_nya) {
     database_nya.execSQL(BUATLAH_TABLE_NYA);
   }

   public static void onUpgrade(SQLiteDatabase database_nya, int versiLama,
       int versiBaru) {
     Log.w(ClassUntukTable.class.getName(), "Database di upgrade dari versi "
         + versiLama + " ke " + versiBaru
         + ", dan akan menghapus semua data lama");
     database_nya.execSQL("DROP TABLE IF EXISTS " + TABLE_NYA);
     onCreate(database_nya);
   }
 }

Menempatkan class-class pada paketnya masing-masing sesuai kegunaan-nya adalah 'best practice' karena dengan cara itu ketika pembangunan aplikasi -nya berkembag atau kalau seandainya mau di rombak maka tak perlu membongkar semua aplikasi tapi yang di rombak hanyalah bagian-bagian kecilnya saja. Misalnya kalau mau rombak databasenya maka tinggal di pilih apanya yang mau di rombak, tambah jumlah kolom?, ganti tipe data? ganti versi aplikasi? apalagi ganti versi aplikasi sangat penting karena versi lama akan secara otomatis di hapus dengan versi baru sehingga tak perlu membebani resource HP pengguna

Masih ada satu class lagi yang di butuhkan untuk merampungkan pembangunan aplikasi ini yaitu class ContentProviderNya.java
Walau namanya ContentProvider tapi sesungguhnya ia tidak menyediakan kontent. dia bekerja mirip 'remote control' pada TV.
Stasiun TV adalah database dan layar TV adalah halaman aplikasi. Kalau tak suka nonton acara di salah satu stasiun TV janganlah ganti TVnya tapi ganti channel-nya saja, dengan memilih nomor sesuai stasiun TVnya di remote. Di content provider inilah tempat link ke database berada, sehingga kalau sumber datanya berubah misalnya dari 'local server' ke 'remote server' maka tak perlu rombak yang lain-lain tapi hanya merubah sedikit pada ContentProviderNya.
Walau nampak rumit melihat kodenya tapi sebenarnya proses kerja dari class ContentProviderNya.java hanya merangkul class aslinya yang telah ada di dalam sistim android bernama ContentProvider.java dengan kata kunci extends
Setelah deklarasikan semua variables yang di butuhkan oleh class ContentProviderNya.java, selebihnya hanya mengikrarkan semua metode bawaan class ContentProvider.java antara lain: onCreate(), query(), getType(), insert(), delete(), update().
Metode-metode bawaan tersebut di tandai dengan @Override maksudnya melangkahi proses kerja asli dari metode tersebut dan memasangnya dengan cara kerja yang sesuai kemauann kita sendiri, misalnya jikalau satu metode mempunyai default latarbelakang putih kita bisa merubahnya menjadi latarbelakang hitam tanpa mengubah sifat-sifat lain dari metode tersebut.

Demi 'best practice' maka antara saya dan aku telah sepakat untuk menempatkan class ContentProviderNya.java pada paket tersendiri yang saya beri nama package com.vik_sintus.bukusakuku.contentprovider_nya;
Untuk lebih jelasnya berikut adalah isi dari ContentProvider.java

package com.vik_sintus.bukusakuku.contentprovider_nya;
/*
 * Copyright (C)Vik Sintus Projects
 *
 * Segala kelebihan dan kekurangan di 
 * luar tanggung jawab pembuat.
 * Di larang memakai kode ini untuk 
 * kepentingan komersial tanpa ijin.
 * Silahkan di pakai untuk kepentingan belajar.
 * vik.sintus@gmail.com
 *
 * Unless required by applicable law or agreed to in writing, 
 * this software is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 * either express or implied.
 * 
 * 
 */
import java.util.Arrays;
import java.util.HashSet;

import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;

import com.vik_sintus.bukusakuku.database_nya.DatabaseHelperNya;
import com.vik_sintus.bukusakuku.database_nya.ClassUntukTable;


public class ContentProviderNya extends ContentProvider {

  // databasenya
  private DatabaseHelperNya databasenya;

  // di gunakan untuk mencocokan data dan
  // mengIdentifikasikan sumber datanya
  private static final int MENGISI_DATA = 10;
  private static final int IDENTITAS_DATA = 20;

  private static final String OTORITAS = "com.vik_sintus.bukusakuku.contentprovider_nya";

  private static final String BASE_PATH_NYA = "meMasukanData";
  public static final Uri CONTENT_URI_NYA = Uri.parse("content://" + OTORITAS
      + "/" + BASE_PATH_NYA);

  public static final String TIPE_CONTENT_NYA = ContentResolver.CURSOR_DIR_BASE_TYPE
      + "/meMasukanData";
  public static final String TIPE_CONTENT_ITEM_NYA = ContentResolver.CURSOR_ITEM_BASE_TYPE
      + "/bukusakuku";

  private static final UriMatcher cocokanURInya = new UriMatcher(UriMatcher.NO_MATCH);
  static {
    cocokanURInya.addURI(OTORITAS, BASE_PATH_NYA, MENGISI_DATA);
    cocokanURInya.addURI(OTORITAS, BASE_PATH_NYA + "/#", IDENTITAS_DATA);
  }
 //override lalu ambil context dari DatabaseHelperNya
  @Override
  public boolean onCreate() {
    databasenya = new DatabaseHelperNya(getContext());
    return false;
  }

  @Override
  public Cursor query(Uri uriNya, String[] apaYangMauDiQuery, String selection,
      String[] selectionArgs, String sortOrder) {

    // pakai SQLiteQueryBuilder ketimbang metode query() 
    SQLiteQueryBuilder queryBuilderNya = new SQLiteQueryBuilder();

    // priksa dulu apakah yang di minta/cari adalah sebuah kolom yang
    // tak eksis 
    priksaKolom(apaYangMauDiQuery);

    // pasang table_nya
    queryBuilderNya.setTables(ClassUntukTable.TABLE_NYA);

    int tipeURInya = cocokanURInya.match(uriNya);
    switch (tipeURInya) {
    case MENGISI_DATA:
      break;
    case IDENTITAS_DATA:
      // tambahkan identitas_data pada query aslinya
      queryBuilderNya.appendWhere(ClassUntukTable.KOLOM_ID + "="
          + uriNya.getLastPathSegment());
      break;
    default:
      throw new IllegalArgumentException("URI_nya tak di kenal: " + uriNya);
    }

    SQLiteDatabase db = databasenya.getWritableDatabase();
    Cursor cursor = queryBuilderNya.query(db, apaYangMauDiQuery, selection,
        selectionArgs, null, null, sortOrder);
    // pastikan semua listeners untuk selalu siap(mendengar)
    cursor.setNotificationUri(getContext().getContentResolver(), uriNya);

    return cursor;
  }

  @Override
  public String getType(Uri uri) {
    return null;
  }

  @Override
  public Uri insert(Uri uriNya, ContentValues dataYangMauDiInsert) {
    int tipeURInya = cocokanURInya.match(uriNya);
    SQLiteDatabase sqlDB = databasenya.getWritableDatabase();
    long id = 0;
    switch (tipeURInya) {
    case MENGISI_DATA:
      id = sqlDB.insert(ClassUntukTable.TABLE_NYA, null, dataYangMauDiInsert);
      break;
    default:
      throw new IllegalArgumentException("URI tak di kenal: " + uriNya);
    }
    getContext().getContentResolver().notifyChange(uriNya, null);
    return Uri.parse(BASE_PATH_NYA + "/" + id);
  }

  @Override
  public int delete(Uri uriNya, String dataYangDiPilih, String[] dataYangAda) {
    int tipeURInya = cocokanURInya.match(uriNya);
    SQLiteDatabase sqlDB = databasenya.getWritableDatabase();
    int barisDataYangTerhapus = 0;
    switch (tipeURInya) {
    case MENGISI_DATA:
      barisDataYangTerhapus = sqlDB.delete(ClassUntukTable.TABLE_NYA, dataYangDiPilih,
          dataYangAda);
      break;
    case IDENTITAS_DATA:
      String id = uriNya.getLastPathSegment();
      if (TextUtils.isEmpty(dataYangDiPilih)) {
        barisDataYangTerhapus = sqlDB.delete(ClassUntukTable.TABLE_NYA,
          ClassUntukTable.KOLOM_ID + "=" + id, 
            null);
      } else {
        barisDataYangTerhapus = sqlDB.delete(ClassUntukTable.TABLE_NYA,
          ClassUntukTable.KOLOM_ID + "=" + id 
            + " and " + dataYangDiPilih,
            dataYangAda);
      }
      break;
    default:
      throw new IllegalArgumentException("URInya tak di kenal: " + uriNya);
    }
    getContext().getContentResolver().notifyChange(uriNya, null);
    return barisDataYangTerhapus;
  }

  @Override
  public int update(Uri uriNya, ContentValues diIsiDenganValue, String dataYangDiPilih,
      String[] dataYangAda) {

    int uriType = cocokanURInya.match(uriNya);
    SQLiteDatabase sqlDB = databasenya.getWritableDatabase();
    int rowsUpdated = 0;
    switch (uriType) {
    case MENGISI_DATA:
      rowsUpdated = sqlDB.update(ClassUntukTable.TABLE_NYA, 
          diIsiDenganValue, 
          dataYangDiPilih,
          dataYangAda);
      break;
    case IDENTITAS_DATA:
      String id = uriNya.getLastPathSegment();
      if (TextUtils.isEmpty(dataYangDiPilih)) {
        rowsUpdated = sqlDB.update(ClassUntukTable.TABLE_NYA, 
            diIsiDenganValue,
            ClassUntukTable.KOLOM_ID + "=" + id, 
            null);
      } else {
        rowsUpdated = sqlDB.update(ClassUntukTable.TABLE_NYA, 
            diIsiDenganValue,
            ClassUntukTable.KOLOM_ID + "=" + id 
            + " and " 
            + dataYangDiPilih,
            dataYangAda);
      }
      break;
    default:
      throw new IllegalArgumentException("URI_nya Tak di kenal: " + uriNya);
    }
    getContext().getContentResolver().notifyChange(uriNya, null);
    return rowsUpdated;
  }

  private void priksaKolom(String[] kolomYangMauDiLihat) {
    String[] dataYangTelahAda = { ClassUntukTable.KOLOM_JENIS,
      ClassUntukTable.KOLOM_JUDUL, ClassUntukTable.KOLOM_ISI,
      ClassUntukTable.KOLOM_ID };
    if (kolomYangMauDiLihat != null) {
      HashSet<String> kolomYangDiCari = new HashSet<String>(Arrays.asList(kolomYangMauDiLihat));
      HashSet<String> kolomYangTersedia = new HashSet<String>(Arrays.asList(dataYangTelahAda));
      // priksa dulu apakah kolom yang di maksud ada atau tidak
      if (!kolomYangTersedia.containsAll(kolomYangDiCari)) {
        throw new IllegalArgumentException("Tak ada kolom dgn nama tsb");
      }
    }
  }

}

Dengan demikian maka semua class java telah selesai di buat yang semuanya berjumlah 5 buah. Kelima file java tersebut seharusnya tersusun di dalam file struktur seperti terlihat pada gambar berikut:


 Selanjutnya kita akan melengkapi aplikasi ini dengan 7 buah file xml dan 2 buah logo yang harus tersusun seperti terlihat pada gambar berikut:

Walau terlihat ada 4 buah logo tetapi saya hanya memakai(memanggil 2 buah)(hanya karena saya malas untuk menghapus logo yang tak terpakai)

Isi dari ke 7 file xml tersebut di atas adalah sbb:

tampil_perbaris.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" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="30dp"
        android:contentDescription="@string/logo"
        android:layout_height="24dp"
        android:layout_marginLeft="4dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:src="@drawable/logo_kecil" />
    

    <TextView
        android:id="@+id/tampil_hanya_judul"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:lines="1"
        android:text="@+id/untuk_baris_judul"
        android:textSize="24sp" />
   

</LinearLayout>


tampilan_bagian_isi.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="match_parent"
    android:orientation="vertical" >

    <Spinner
        android:id="@+id/jenisnya"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:entries="@array/priorities" >
    </Spinner>

    <LinearLayout
        android:id="@+id/LinearLayout01"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <EditText
            android:id="@+id/isilah_judulnya"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="@string/isilah_judulnya"
            android:imeOptions="actionNext" >
        </EditText>
    </LinearLayout>

    <EditText
        android:id="@+id/isilah_isinya"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:gravity="top"
        android:hint="@string/isilah_isinya"
        android:imeOptions="actionNext" >
    </EditText>

    <Button
        android:id="@+id/tombol_simpan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/simpanlah" >
    </Button>

</LinearLayout>


tampilan_depan.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="match_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </ListView>

    <TextView
        android:id="@android:id/empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tak_ada_isi" />

</LinearLayout>


untuk_menu_utama.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menulis_data"
        android:showAsAction="always"
        android:title="Tulis">
    </item>

</menu>


priority.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string-array name="priorities">
        <item>Segera</item>
        <item>Ingat</item>
    </string-array>

</resources>


strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="hello">BukuSakuKu</string>
  <string name="nama_aplikasi">BukuSakuKu</string>
  <string name="tak_ada_isi">Tak ada isi</string>
  <string name="tambahkan">Tambahkan</string>
  <string name="hapus_judul">Hapus</string>
  <string name="judulnya">judul</string>
  <string name="hapus_isinya">Hapus</string>
  <string name="isilah_judulnya">Judulnya</string>
  <string name="isilah_isinya">Isinya</string>
  <string name="simpanlah">Simpan</string>
  <string name="logo">BukuSakuKu</string>
</resources>


styles.xml
<resources>

    <style name="AppTheme" parent="android:Theme.Light" />

</resources> 


Semua file yang di butuhkan telah di pasang,.. silahkan di kembangkan dan kalau perlu di upload ke market

4 comments:

  1. Assalamu Alaikum
    mas, bisa bimbing untuk pembuatan program untuk skripsiku? nanti kita bicarakan honornya....

    contact me :
    HP : 085397387907
    Wechat : r_besarr
    fb : www.facebook.com/andyaang

    saya tunggu mas ...

    ReplyDelete
  2. sorry mas Andy, utk sementara saya terlalu banyak pekerjaan, banyak klien yang bersungut2. tetapi saya selalu ingin mencoba membantu kalau saya bisa... projectnya seperti apa?
    Di blog ini saya selalu memasang project kecil2-an yang sudah rampung, silahkan di modify dan di kembangkan

    ReplyDelete
  3. saya mau tanya mastah vik Sintus kalau ingin membuat speech recognotion yang offline bagaimana?

    ReplyDelete
    Replies
    1. bro Natanael... coba lihat http://belajar-android-indonesia.blogspot.com.au/2012/03/aplikasi-agar-hp-bisa-mendengar-dan.html

      Tapi yang di recognise adalah hanya kalau kita ngomong inggris, german, rusia, spanyol.

      Dalam link tsb di atas saya pasang kode agar app-nya mengerti bahasa inggris

      Delete