Phoenix Elixir – Make CRUD with PostgreSQL

Phoenix Elixir – Make CRUD with PostgreSQL

Sebelumnya perkenalkan Phoenix Framework.. adalah framework yang dibangun di atas bahasa pemrograman Elixir, lebih lengkap tentang Elixir dapat dilihat di http://elixir-lang.org/ dan Phoenix dapat dilihat di http://www.phoenixframework.org/. Code Elixir sendiri akan di compile ke Erlang byte code dan berjalan di atas Erlang VM, karena itu kita harus memiliki Erlang untuk menjalankan Elixir.

Proses installasi Phoenix Framework dapat dilihat pada tautan berikut http://www.phoenixframework.org/docs/installation , tautan tersebut akan memaparkan proses installasi Phoenix, Elixir, Erlang dan tools lain yang dibutuhkan untuk menjalankan Phoenix Framework.

Pada postingan ini saya tidak memaparkan bagaimana proses installasi dan proses membuat project baru pada Phoenix Framework (saya asumsikan sudah bisa) tapi jika anda masih belum tahu cara membuat project baru pada Phoenix, silahkan lihat pada tautan berikut http://www.phoenixframework.org/docs/up-and-running.

Setelah membuat project baru kita akan coba memahami bagaimana membuat operasi CRUD pada Phoenix. Saya akan menggunakan database postgreSQL (karena secara default di support Phoenix dan update terbaru dengan fitur yang bagus). Phoenix menyediakan fitur generate operasi CRUD dimana dapat menghemat waktu kita dalam mengembangkan aplikasi. Pada postingan ini kita akan generate CRUD tabel “users” (tabel “users” belum dibuat pada database postgreSQL, phoenix akan melakukannya).

Masukkan perintah berikut :

$ mix phoenix.gen.html User users name:string email:string birth:date age:integer
* creating web/controllers/user_controller.ex
* creating web/templates/user/edit.html.eex
* creating web/templates/user/form.html.eex
* creating web/templates/user/index.html.eex
* creating web/templates/user/new.html.eex
* creating web/templates/user/show.html.eex
* creating web/views/user_view.ex
* creating test/controllers/user_controller_test.exs
* creating web/models/user.ex
* creating test/models/user_test.exs
* creating priv/repo/migrations/20161224162452_create_user.exs

Add the resource to your browser scope in web/router.ex:
    resources "/users", UserController

Remember to update your repository by running migrations:
    $ mix ecto.migrate

Perintah di atas mendefinisikan data untuk tabel “users” yang akan disimpan dalam model “User”. Pada perintah kita harus mendefinisikan field yang didefinisikan pada tabel users beserta tipe datanya. Perintah di atas akan men-generate file eex elixir yang digunakan untuk operasi CRUD. Apakah tabel users pada database sudah dibuat? Belum.. kita akan memasukkan perintah lain untuk melakukannya..

Lakukan modifkasi pada web/route.ex :

scope "/", FirstApp do
    pipe_through :browser # Use the default browser stack
    get "/", PageController, :index
    resources "/users", UserController
  end

Setelah memodifikasi code pada web/route.ex masukkan perintah berikut pada terminal :

mix ecto.migrate
Compiling 11 files (.ex)
Generated first_app app
23:28:53.583 [info]  == Running FirstApp.Repo.Migrations.CreateUser.change/0 forward
23:28:53.583 [info]  create table users
23:28:53.616 [info]  == Migrated in 0.0s

Perintah di atas akan menjalankan fungsi ecto.migrate. Perintah tersebut akan menjalankan semua file yang ada pada direktori “priv/repo/migrations” dimana migrations yang dilakukan berkaitan dengan database. Saat ini kita memiliki sebuah file pada “priv/repo/migrations” yaitu file yang di generate dari proses sebelumnya. Perintah di atas secara otomatis akan membuat sebuah tabel pada database anda dengan schema seperti berikut :

screen-shot-2016-12-24-at-11-29-29-pm

Pada perintah sebelumnya kita hanya membuat kolom name, email, birth dan age. Phoenix secara otomatis akan menambahakan kolom id sebagai primary key dan kolom inserted_at dan updated_at dari fungsi timestamp yang dimiliki phoenix.

Setelah tabel “users” dibuat pada database, barulah kita dapat menjalankan proses CRUD yang telah di generate sebelumnya. Jalankan perintah berikut pada terminal :

$ mix phoenix.server

Masukkan url berikut : http://localhost:4000/users , akan tampil halaman pengelolaan data “users” seperti berikut :

screen-shot-2016-12-25-at-12-27-03-am

Jika memilih menu “New user” aplikasi akan mengarahkan anda pada halaman untuk menambah data user baru :

screen-shot-2016-12-25-at-12-27-11-am

Khusus input data dengan type “date” phoenix secara otomatis membuat form input berupa dropdown dengan pilihan tahun, bulan dan tanggal. Form yang digenerate juga memiliki validasi sebagai contoh jika anda klik “Submit” tanpa mengisi data akan muncul halaman dengan tampilan error seperti berikut :

screen-shot-2016-12-25-at-1-11-05-am

Berikut tampilan halaman user ketika sudah menambah data baru :

screen-shot-2016-12-25-at-12-27-59-am

Pada halaman tersebut disediakan fitur untuk melakukan show, edit dan delete data. Silahkan mencoba masing-masing fitur..

Bekerja dengan framework yang memiliki fitur generate sering kali membuat programmer pemula jadi tidak memahami code hasil proses generate yang dilakukan. Untuk itu mari kita pahami code yang ada di balik proses generate tersebut.

Phoenix Framework menggunakan konsep MVC (Model – View – Controller) yang artinya kebanyakan proses pada Phoenix Framework akan berputar-putar pada ketiga komponen tersebut (bagi anda yang belum memahami apa itu MVC silahkan googling ya…). Proses generate yang dilakukan oleh Phoenix akan membuat beberapa file pada folder:

  • web/controllers/ (controller)
  • web/models (models)
  • web/templates/user (views)
  • web/views
  • test/controllers
  • test/models
  • priv/repo/migrations

Pada priv/repo/migrations kita akan melihat file migration createuser :

defmodule FirstApp.Repo.Migrations.CreateUser do
  use Ecto.Migration
  def change do
    create table(:users) do
      add :name, :string
      add :email, :string
      add :birth, :date
      add :age, :integer
      timestamps()
    end
  end
end

Pada code di atas dapat dilihat bahwa migration akan membuat sebuah tabel user dengan kolom yang sudah kita definisikan pada terminal ditambah dengan “timestamps()” yang terdiri dari kolom inserted_at dan updated_at.

MODEL

Model digunakan untuk mendefinisikan data pada suatu tabel dalam database termasuk jenis data, validasi, query, dll. Proses generate oleh phoenix akan meletakkan file model dalam folder “web/models” pada kasus ini pada file “web/models/user.ex”. Code dalam model user adalah seperti berikut :

defmodule FirstApp.User do
  use FirstApp.Web, :model

  schema "users" do
    field :name, :string
    field :email, :string
    field :birth, Ecto.Date
    field :age, :integer
    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :email, :birth, :age])
    |> validate_required([:name, :email, :birth, :age])
  end
end

Code di atas merupakan class FirstApp.User merupakan sebuah model. Pada class tersebut terdiri dari definisi schema model Users yang mendefinisikan nama field dan type data masing-masing field. Field “inserted_at” dan “updated_at” didefinisikan pada fungsi “timestamps()”.

Model juga mendefinisikan changeset, changeset ke depannya akan mendefinisikan data yang digunakan untuk memanipulasi suatu data. Changeset pada code di atas terdiri dari “cast” yang mendefinisikan field yang akan menjadi parameter pada proses modifikasi data dan sebagai daftar field yang akan di update (cast hanya dapat berisi nama field yang telah didefinisikan pada schema. Changeset di atas juga terdiri dari “validate_required” yang berisi kumpulan field yang wajib diisi ketika proses modifikasi.

CONTROLLER DAN VIEW

Controller adalah komponen yang melakukan suatu action dan menterjemahkan request suatu user. Ketika user melakukan request suatu action maka controller akan menterjemahkan permintaan user dan menentukan UI (biasanya memanggil suatu view) dan data (diambil dari model) yang akan ditampilkan kepada user. File controller yang digenerate oleh phoenix ada pada “web/controller/user_controller.ex” sedangkan views ada pada folder “web/templates/user” dan “web/views/user_view.ex”. Baiklah pertama-tama kita akan memahami bagaimana url http://localhost:4000/users yang kita akses akan menampilkan kita halaman daftar user yang sudah ada pada database.

Ketika kita melakukan request suatu url, aplikasi akan menangkap request url dari file “web/router.ex” tepatnya pada code :

 scope "/", FirstApp do
    pipe_through :browser # Use the default browser stack
    get "/", PageController, :index
    resources "/users", UserController
  end

Code di atas telah mendefinisikan seluruh resources yang ada pada controller user agar dapat diakses dengan url “/users”, adapaun jenis fungsi yang dapat dijalankan pada url mengikuti nama masing-masing fungsi dalam UserController.

FUNGSI INDEX

Mengakses url http://localhost:4000/users secara otomatis akan mengarahkan anda mengakses fungsi “index” pada UserController. Perintah yang dijalankan adalah : 

def index(conn, _params) do
    users = Repo.all(User)
    render(conn, "index.html", users: users)
  end

Yang terjadi pada code di atas adalah :

  • Mendefinisikan variabel users yang diisi dengan Repo.all(User), fungsi Repo.all(User) akan menampilkan seluruh data yang ada pada tabel User. UserController telah mendefinisikan code “  alias FirstApp.User” yang artinya kita tidak perlu memanggil model User dengan memanggil FirstApp.User tetapi cukup “User” saja.
  • Me-render halaman index.html (pada folder “web/template/users”) dan mengirimkan variabel “users” dengan nama users ke halaman yang di-render.

Halaman index.html yang di-render pada fungsi index adalah halaman index.html pada direktori “web/template/users/“ dimana memiliki code sebagai berikut :

<h2>Listing users</h2>
<table class="table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Birth</th>
      <th>Age</th>
      <th></th>
    </tr>
  </thead>

  <tbody>
<%= for user <- @users do %>
    <tr>
      <td><%= user.name %></td>
      <td><%= user.email %></td>
      <td><%= user.birth %></td>
      <td><%= user.age %></td>
      <td class="text-right">
        <%= link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs" %>
        <%= link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs" %>
        <%= link "Delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %>
      </td>
    </tr>
<% end %>

  </tbody>
</table>
<%= link "New user", to: user_path(@conn, :new) %>

Code di atas akan menampilkan sebuah tabel dengan head Name, Email, Birth, Age dan sebuah head tanpa label. Isi dari tabel tersebut diisi dengan melakukan looping data user yang menampilkan field name, email, birth dan age. Sedangkan satu kolom lagi akan diisi dengan tombol Show, Edit dan Delete untuk masing-masing data.

MENAMBAH DATA BARU

Untuk menambah data baru kita akan melibatkan dua buah fungsi pada controller yaitu “new” (menampilkan form input data) dan “create” (memproses data yang telah diinput pada form). Code fungsi “new” adalah sebagai berikut :

  def new(conn, _params) do
    changeset = User.changeset(%User{})
    render(conn, "new.html", changeset: changeset)
  end

Yang terjadi pada code di atas adalah :

  • mendefinisikan variabel “changeset” dengan changeset pada model User dengan kondisi seluruh field yang masih kosong.
  • me-render halaman “new.html” dan mengirimkan variabel changeset.

Code “new.html” adalah sebagai berikut :

<h2>New user</h2>
<%= render "form.html", changeset: @changeset,
                        action: user_path(@conn, :create) %>
<%= link "Back", to: user_path(@conn, :index) %>

Code new.html me-render kembali halaman “form.html” (halaman ini juga digunakan ulang pada halaman update) dan mengirim variabel changeset yang berasal dari controller sebelumnya dan mengirimkan variabel “action” yang merupakan url dari fungsi “create” pada UserController. Silahkan lihat code “form.html.eex” anda. Code tersebut berupa form yang terdiri dari beberapa macam input data. Ketika tombol “Submit” dipilih, maka form di atas akan menjalankan fungsi “create” pada UserController. Fungsi “create” sendiri akan menjalankan code berikut :

  def create(conn, %{"user" => user_params}) do
    changeset = User.changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, _user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

Yang terjadi pada code di atas adalah :

  • “create” akan menangkap inputan dari form “new.html” melalui parameter “user”
  • mendefinisikan variabel “changeset” yang diisi oleh changeset pada model User dan field pada changeset akan diisi oleh parameter “user_params” yang didapat dari inputan pada form.
  • dilakukan proses input data changeset ke dalam database.
  • Jika data berhasil disimpan ke dalam database maka fungsi akan menampilkan pesan sukses (fungsi put_flash digunakan untuk menampilkan suatu pesan pada tampilan layout) dan me-redirect ke halaman index.
  • Jika data gagal disimpan maka aplikasi akan me-render halaman “new.html” kembali beserta changeset yang telah di definisikan sebelumnya. Changeset yang dikirim juga akan membawa serta pesan error yang terjadi pada inputan sebelumnya sehingga penguin dapat melihat validasi yang gagal dipenuhi ketika proses insert data.

MENAMPILKAN DETAIL DATA

Fungsi untuk menampilkan detail data ada pada code :

def show(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    render(conn, "show.html", user: user)
  end

Dimana code di atas akan melakukan tahapan sebagai berikut :

  • Mendefinisikan variabel user yang diisi dengan data dari tabel user yang memiliki primary key atau id sesuai dengan parameter yang di dapat dari url. Fungsi “Repo.get!(User,id)”  artinya data akan diambil melalui model User.
  • Me-render halaman “show.html” dan mengirimkan variabel user yang telah didefinisikan sebelumnya.

Halaman show.html akan menampilkan data kepada user dengan code sebagai berikut :

<h2>Show user</h2>
<ul>
  <li>
    <strong>Name:</strong>
    <%= @user.name %>
  </li>

  <li>
    <strong>Email:</strong>
    <%= @user.email %>
  </li>

  <li>
    <strong>Birth:</strong>
    <%= @user.birth %>
  </li>

  <li>
    <strong>Age:</strong>
    <%= @user.age %>
  </li>
</ul>

<%= link "Edit", to: user_path(@conn, :edit, @user) %>
<%= link "Back", to: user_path(@conn, :index) %>

Code di atas menampilkan masing-masing field pada tabel user ke dalam tag HTML yang telah disusun. Pada HTML tersebut juga ditampilkan tautan untuk melakukan fungsi edit dan fungsi back ke halaman index.

MEMPERBAHARUI DATA

Proses memperbaharui/update data akan ditampilkan pada fungsi “edit” dan “update. Cara kerja kedua fungsi ini sama dengan fungsi “new” dan “create” pada saat kita ingin menambahkan data baru. Perbedaan pada kedua proses ini adalah fungsi “edit” yang mendefinisikan changeset dengan mengambil data dari tabel user dengan id/primary key tertentu :

 def edit(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    changeset = User.changeset(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

Pada code tersebut changeset akan mendefinisikan changeset dari model User dimana isi dari changeset akan mengambil data pada tabel user yang memiliki id tertentu.

MENGHAPUS DATA

Perintah delete data akan menjalankan code berikut :

def delete(conn, %{"id" => id}) do
    user = Repo.get!(User, id)
    # Here we use delete! (with a bang) because we expect
    # it to always work (and if it does not, it will raise).

    Repo.delete!(user)
    conn
    |> put_flash(:info, "User deleted successfully.")
    |> redirect(to: user_path(conn, :index))
  end

Code tersebut akan mendefinisikan variabel user yang berisi data user yang memiliki id sesuai request. Data user akan digunakan untuk perintah hapus data dimana setelah proses hapus data berjalan aplikasi akan menampilkan pesan sukses hapus data dan me-redirect pengguna ke halaman index awal.

MODIFIKASI APLIKASI

PENYEMPURNAAN FORM

Code yang telah di generate oleh Phoenix tidak selalu dapat memenuhi kebutuhan kita. Untuk itu diperlukan proses modifikasi code secara manual oleh programmer. Pertama-tama mari kita sempurnakan tampilan form input data. Form input data memiliki sebuah input khusus untuk input nilai date (tanggal) dimana inputan tersebut akan menampilkan 3 buah dropdown tahun, bulan dan tanggal. Dropdown tahun hanya menampilkan sepuluh tahun terakhir dari tahun saat ini, padahal kita ingin menyediakan opsi dari tahun 1960 sampai 2010. Lakukan modifikasi pada code “web/templates/user/form.html.eex” tepatnya pada baris code berikut :

<%= date_select f, :birth, class: "form-control" %>

lakukan perubahan menjadi seperti berikut :

    <%= date_select f, :birth, year: [options: 1960..2010], class: "form-control" %>

Jika anda jalankan aplikasi maka dropdown tahun akan menampilkan opsi tahun dari tahun 1960 hingga tahun 2010.

Untuk memberikan nilai default pada tahun dapat memasukkan code sebagai berikut :

    <%= date_select f, :birth, year: [value: 1989 ,options: 1960..2010], class: "form-control" %>

Jika anda ingin membuat rentang tahun dari tahun 1960 sampai dengan tahun saat ini dapat membuat code seperti berikut :

    <%= date_select f, :birth, year: [value: 1989 ,options: 1960..DateTime.utc_now.year], class: "form-control" %>

VALIDASI

Sebagai contoh kita ingin menambahkan validasi berikut :

  • Nama harus memiliki panjang minimal 3 huruf
  • Email memiliki panjang minimal 5 huruf
  • Email harus memiliki format email yang benar, jika format email salah maka akan mengirimkan pesan error “Please fix your email format”

Phoenix meletakkan segala hal yang berkaitan dengan data (termasuk validasi) pada Model. Untuk menambahkan validasi di atas kita dapat melakukan modifikasi model User tepatnya pada changeset menjadi seperti berikut :

def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:name, :email, :birth, :age])
    |> validate_required([:name, :email, :birth, :age])
    |> validate_length(:name, min: 3)
    |> validate_length(:email, min: 5)
    |> validate_format(:email, ~r/@/, message: "Please fix your email format")
  end
  • validate_length digunakan membatasi panjang suatu field. Pada code di atas kita memberikan validasi field name minimal terdiri dari 3 huruf dan field email minimal terdiri dari 5 huruf. Selain membatasi panjang minimal anda juga dapat membatasi panjang maksimal (gunakan “max”) dan membuat suatu field harus memiliki panjang tertentu (gunakan “is”).
  • validate_format digunakan untuk menentukan format suatu  inputan data. Pada code di atas validate_format mengharuskan field email memiliki karakter “@“ di dalamnya.
  • validate_format di atas juga akan mengeluarkan pesan error “Please fix your email format” jika pengguna tidak mengikuti format yang telah ditentukan.

Selesai.. itulah sedikit perkenalan dengan Phoenix Framework..

Xamarin iOS – Expendable UITableView

Xamarin iOS – Expendable UITableView

Anggaplah saya ingin menampilkan sekumpulan data klub sepak bola ke dalam UITableView. Jika salah satu klub sepak bola di-klik, akan menampilkan daftar pemain pada data tersebut seperti gambaran berikut :

jan-03-2017-10-16-37

Pertama buatlah model untuk mendefinisikan data SoccerClub pada file “SoccerClubModel.cs”, masukkan code berikut :

 using System;
 using System.Collections.Generic;
 
 namespace ios_playground
 {
     public class SoccerClubModel
     {
         private List<string> players;
         private string name;
 
         public SoccerClubModel(string name)
         {
             this.name = name;
             players = new List<string>();
         }
 
         public string Name { 
             get { return this.name;}
         }
 
         public List<string> Players
         {
             get { return this.players; }
             set { this.players = value;}
         }
     }
 }

Code tersebut mendefinisikan class SoccerClubModel dengan variabel nama klub sepak bola dan list nama pemainnya.

Tambahkan sebuah UIViewController dan tambahkan UITableView di dalamnya. Beri nama UITableView tersebut dengan nama “tableExpend”. Paad UITableView yang telah ditambahkan terdapat sebuah UITableViewCell, atur property “Style” -nya menjadi “Right Detail” dan beri identifier “CellChild”.

Kita akan memasuki bagian utama yaitu membuat UITableViewSource yang dapat mendefinisikan Expendable table. Buatlah sebuah class “ExpendableSource” yang akan meng-extend class UITableViewSource. Masukkan code berikut :

 using System;
 using System.Collections.Generic;
 using ObjCRuntime;
 using Foundation;
 using UIKit;
 using CoreGraphics;
 
 namespace ios_playground
 {
     public class ExpendableSource : UITableViewSource
     {
         List<SoccerClubModel> datas;
         NSString childIdentifier = new NSString("CellChild");
         UITableView parentTable;
 
         private bool[] _isSectionOpen;
         private EventHandler _buttonClick;
 
         public ExpendableSource(UITableView parentTable,List<SoccerClubModel> datas)
         {
             this.datas = datas;
             this.parentTable = parentTable;
 
             _isSectionOpen = new bool[datas.Count];
 
             _buttonClick = (sender, e) =>
             {
                 var button = sender as UIButton;
                 var section = button.Tag;
                 _isSectionOpen[(int)section] = !_isSectionOpen[(int)section];
 
                 parentTable.ReloadData();
                 var paths = new NSIndexPath[RowsInSection(parentTable, section)];
                 for (int i = 0; i < paths.Length; i++)
                 {
                     paths[i] = NSIndexPath.FromItemSection(i, section);
                 }
 
                 parentTable.ReloadRows(paths, UITableViewRowAnimation.Automatic);
             };
         }
 
         public override nint NumberOfSections(UITableView tableView)
         {
             return datas.Count;
         }
 
         public override nint RowsInSection(UITableView tableview, nint section)
         {
             return _isSectionOpen[(int)section] ? datas[(int)section].Players.Count : 0;
         }
 
 
         public override nfloat GetHeightForHeader(UITableView tableView, nint section)
         {
             return 44f;
         }
 
         public override nfloat EstimatedHeightForHeader(UITableView tableView, nint section)
         {
             return 44f;
         }
 
         public override UIView GetViewForHeader(UITableView tableView, nint section)
         {
             UIView view = CustomView(datas[(int)section].Name, (int)section);
             return view;
         }
 
         public UIView CustomView(string caption,nint tag)
         {
             UIView view = new UIView(new System.Drawing.RectangleF(0, 0, 320, 20));
             view.BackgroundColor = UIColor.White;
 
             UILabel label = new UILabel();
             label.Font = UIFont.FromName("Helvetica-Bold", 16f);
             label.Frame = new System.Drawing.RectangleF(5, 10, 315, 20);
             label.Text = caption;
             view.AddSubview(label);
 
             UIButton hiddenButton = new UIButton(view.Bounds);
             hiddenButton.Tag = tag;
             hiddenButton.TouchUpInside += _buttonClick;
             view.AddSubview(hiddenButton);
 
             return view;
         }
 
         public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
         {
             UITableViewCell cell = tableView.DequeueReusableCell(childIdentifier);
             if (cell == null)
                 cell = new UITableViewCell(UITableViewCellStyle.Default, childIdentifier);
 
             cell.TextLabel.Text = datas[indexPath.Section].Players[indexPath.Row];
             cell.DetailTextLabel.Text = datas[indexPath.Section].Name;
 
             return cell;
 
         }
     }
 }
 

Berikut penjelasan code di atas :

  • Mendefinisikan variabel datas untuk menyimpan data yang ditampilkan, childIdentifier sebagai identifier dari UITableViewCell yang digunakan, dan parentTable yang akan menghubungkan class dengan tabel yang menggunakannya. Perlakuan yang terjadi pada variabel “parentTabel” akan berpengaruh langsung pada table yang memanggil class ini.
  • Pendefinisian variabel _isSectionOpen untuk variabel penyimpan data mana saja yang sedang menampilkan dan menyembunyikan daftar nama pemain, _buttonClick yang akan menyimpan suatu event.
  • Constructor akan mendefinisikan tableView, datas serta proses yang dilakukan untuk event _buttonClick
  • NumberOfSections sebagai jumlah section pada tabel
  • RowsInSection sebagai jumlah row pada masing-masing section
  • GetHeightForHeader dan EstimatedHeightForHeader untuk tinggi header
  • GetViewForHeader menentukan UIView apa yang ditampilkan pada section header. Header sendiri akan memanggil UIView dari fungsi CustomView() dimana fungsi ini akan menghasilkan sebuah UIView yang terdiri dari sebuah UILabel beserta hidden UIButton yang ketika di klik akan menjalankan event _buttonClick
  • Yang terakhir fungsi GetCell() adalah code yang menampilkan data pada masing-masing row data nama pemain sepak bola.

Selesai.. silahkan running aplikasi anda..

Thanks to https://github.com/b099l3/StackOverflow/tree/master/ExpandableTableView

Code dari tutorial ini dapat juga dilihat di https://github.com/sabithuraira/xamarin_ios_playground/commit/1eccd61bee5720009aef8af99613245bc1161c4d

[Xamarin iOS] Consuming REST API

Setelah memahami cara mengkonsumsi REST API dengan Xamarin android pada tautan berikut, sekarang kita akan mencoba penerapan pada aplikasi iOS dengan Xamarin.

Buatlah sebuah aplikasi iOS pada Xamarin. Design main.storyboard anda seperti berikut :

screen-shot-2016-12-24-at-1-38-22-am

Main.storyboard di atas terdiri dari :

  • UINavigationController
  • MainController : terhubung langsung dengan UINavigationController. MainController terdiri dari sebuah UITableView (kita beri nama “TabelFood”) dan sebuah UITextField (diberi nama “txtTitle”). UITableView secara otomatis memiliki sebuah UITableViewCell, atur style-nya menjadi “Right Detail” dan berilah identifier-nya “CellData”.
  • FormController : Controller ini digunakan untuk melakukan proses edit data (insert, update dan delete). FormController terdiri dari dua buah UITextField guna input data baru yaitu txtName dan txtPrice.
  • Sebuah segue dari UINavigationController ke MainController dengan type segue “root”
  • Sebuah segue dari MainController ke FormController dengan  type “show” dan nama segue “editSegue”

Setelah selesai melakukan pengaturan UI pada masing-masing controller buatlah class FoodMenu.cs sebagai model dari data Food. Masukkan code berikut :

 using System;
 namespace silexiOS
 {
     public class FoodMenu
     {
         private int id;
         private string name;
         private double price;
 
         public FoodMenu(string name, double price)
         {
             this.name = name;
             this.price = price;
         }
 
         public FoodMenu(int id, string name, double price)
         {
             this.id = id;
             this.name = name;
             this.price = price;
         }
 
         public int Id { get { return this.id; } }
 
         public string Name { get { return this.name; } }
         public double Price { get { return this.price; } }
     }
 }
 

Tambahkan package “Newtonsoft.Json” pada project anda untuk memudahkan mengkonsumsi json format yang didapat dari REST API. Buatlah sebuah fungsi “FoodLoader.cs” untuk mengakses REST API dari server, masukkan code berikut :

 using System;
 using System.Collections.Generic;
 using System.Text;
 using System.IO;
 using System.Net;
 using Newtonsoft.Json.Linq;
 
 namespace silexiOS
 {
     public class FoodLoader
     {
         private static readonly string serverLink = "http://localhost/medical/web/index2.php/";
 
         public FoodLoader()
         { }
 
         public static List<FoodMenu> LoadData()
         {
             string link = serverLink + "daftar";
             List<FoodMenu> datas = new List<FoodMenu>();
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
                 request.Method = "GET";
                 request.ContentType = "application/json";
 
                 HttpWebResponse response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     string outputString = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
 
                     outputString = outputString.Trim().Replace("// ", "");
                     var resultArray = JArray.Parse(outputString);
 
                     for (int i = 0; i < resultArray.Count; i++)
                     {
                         var jstock = (JObject)resultArray[i];
 
                         FoodMenu temp_data = new FoodMenu(
                             int.Parse((string)jstock["id"]),
                             (string)jstock["name"],
                             double.Parse((string)jstock["price"])
                         );
 
                         datas.Add(temp_data);
                     }
                 }
             }
             catch (Exception e)
             {
                 throw;
             }
             return datas;
         }
 
         public static String InsertData(FoodMenu data)
         {
             string link = serverLink + "insert";
             String result = "";
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
 
                 StringBuilder postData = new StringBuilder();
                 postData.Append("name=" + data.Name + "&");
                 postData.Append("price=" + data.Price.ToString());
                 var data_encode = Encoding.ASCII.GetBytes(postData.ToString());
 
                 request.Method = "POST";
                 request.ContentType = "application/x-www-form-urlencoded";
                 request.ContentLength = data_encode.Length;
 
                 using (var stream = request.GetRequestStream())
                 {
                     stream.Write(data_encode, 0, data_encode.Length);
                 }
 
                 var response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     result = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
                 }
             }
             catch (Exception)
             {
                 throw;
             }
             return result;
         }
 
         public static String UpdateData(FoodMenu data, int id)
         {
             string link = serverLink + "update/" + id.ToString();
             String result = "";
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
 
                 StringBuilder postData = new StringBuilder();
                 postData.Append("name=" + data.Name + "&");
                 postData.Append("price=" + data.Price.ToString());
                 var data_encode = Encoding.ASCII.GetBytes(postData.ToString());
 
                 request.Method = "PUT";
                 request.ContentType = "application/x-www-form-urlencoded";
                 request.ContentLength = data_encode.Length;
 
                 using (var stream = request.GetRequestStream())
                 {
                     stream.Write(data_encode, 0, data_encode.Length);
                 }
 
                 var response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     result = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
                 }
             }
             catch (Exception)
             {
                 throw;
             }
             return result;
         }
 
         public static String DeleteData(int id)
         {
             string link = serverLink + "delete/" + id.ToString();
             String result = "";
             try
             {
                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
                 request.Method = "DELETE";
                 var response = (HttpWebResponse)request.GetResponse();
 
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     Stream receiveStream = response.GetResponseStream();
                     StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
                     result = readStream.ReadToEnd();
 
                     response.Close();
                     readStream.Close();
                 }
             }
             catch (Exception)
             {
                 throw;
             }
             return result;
         }
     }
 }
 

Dua buah class di atas sama dengan class yang di definisikan pada aplikasi Android yang telah dibuat sebelumnya.

Buatlah class “FoodAdapter.cs” dimana class ini merupakan source yang digunakan untuk mengisi row data pada TabelFood yang ada pada MainController.

 using System;
 using System.Collections.Generic;
 using UIKit;
 using Foundation;
 
 namespace silexiOS
 {
     public class FoodAdapter : UITableViewSource
     {
         private MainController parent;
         private List<FoodMenu> datas = new List<FoodMenu>();
         private string cellIdentifier = "CellData";
 
         public FoodAdapter(MainController parent, List<FoodMenu> datas)
         {
             this.parent = parent;
             this.datas = datas;
         }
 
         public override nint RowsInSection(UITableView tableview, nint section)
         {
             return this.datas.Count;
         }
 
         public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
         {
             var cell = tableView.DequeueReusableCell(cellIdentifier);
 
             if (cell == null)
             {
                 cell = new UITableViewCell(UITableViewCellStyle.Default, cellIdentifier);
             }
 
             cell.TextLabel.Text = this.datas[indexPath.Row].Name;
             cell.DetailTextLabel.Text = this.datas[indexPath.Row].Price.ToString();
             return cell;
         }
     }
 }
 

Sekarang kita akan mulai masuk mengelola code pada MainController dan FormController yang menjalankan proses utama dari aplikasi yang dibuat. Masukkan code berikut pada MainController :

 using Foundation;
 using System;
 using System.Collections.Generic;
 using UIKit;
 
 namespace silexiOS
 {
     public partial class MainController : UIViewController
     {
         public int EditPosition = -1;
         List<FoodMenu> datas = new List<FoodMenu>();
 
         public MainController (IntPtr handle) : base (handle)
         {
         }
 
         public override void ViewWillAppear(bool animated)
         {
             base.ViewWillAppear(animated);
             InitializeView();
         }
 
         public override void ViewDidLoad()
         {
             base.ViewDidLoad();
             this.InitializeBar();
         }
 
         private void InitializeBar()
         {
             List<UIBarButtonItem> rightButtons = new List<UIBarButtonItem>();
 
             rightButtons.Add(new UIBarButtonItem(
                 UIBarButtonSystemItem.Add,
                 (sender, e) => { PerformSegue("editSegue", this); }
             ));
 
             rightButtons.Add(new UIBarButtonItem(
                 UIBarButtonSystemItem.Refresh,
                 (sender, e) => { this.InitializeView(); }
             ));
 
             NavigationItem.RightBarButtonItems = rightButtons.ToArray();
         }
 
         private void InitializeView()
         {
             this.EditPosition = -1;
             txtTitle.Text = "Loading...";
 
             this.datas = FoodLoader.LoadData();
             TabelFood.Source = new FoodAdapter(this, this.datas);
             TabelFood.ReloadData();
 
             txtTitle.Text = "List Food";
         }
 
         public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
         {
             base.PrepareForSegue(segue, sender);
             if (segue.Identifier == "editSegue")
             {
                 var vc = segue.DestinationViewController as FormController;
             }
         }
     }
 }

Yang terjadi pada MainController adalah :

  • Terdapat 2 variabel yang didefinisikan yaitu “EditPosition” dan “datas”. EditPosition digunakan untuk mengetahui posisi data yang di-klik oleh user (ingat kita akan membuat fungsi edit dan delete), sedangkan “datas” akan menyimpan data daftar makanan yang di dapatkan dari REST API.
  • Jika nilai “EditPosition” lebih kecil dari 0 (nol) artinya tidak ada data yang dipilih oleh pengguna.
  • Ketika screen akan muncul maka akan dijalankan fungsi ViewWillAppear dimana fungsi ini akan menjalankan fungsi “InitializeView()”
  • Fungsi “InitializeView()” proses utama-nya adalah mengunduh data yang disediakan dengan REST dimana data yang diunduh langsung ditampilkan pada “TabelFood” yang ada pada screen.
  • Selain menampilkan data ke dalam TabelFood, InitializeView() juga akan memberikan nilai pada “EditPosition” menjadi -1 (ketika kita memanggil/refresh data sudah pasti belum ada data yang dipilih) dan mengeset nilai txtTitle guna menampilkan indikator proses unduh data. Jika proses unduh sedang berjalan, maka txtTitle akan menampilkan tulisan “Loading…”.
  • Setelah menjalankan ViewWillAppear, MainController menjalankan ViewDidLoad() ketika screen telah di load.
  • ViewDidLoad akan menjalankan fungsi “InitializeBar()” dimana fungsi ini akan menambahkan dua buah bar menu pada screen yaitu menu “Add” dan menu “Refresh”.
  • Ketika menu “Add” dipilih maka akan menjalankan fungsi untuk menjalankan segue dimana Controller segue yang dituju adalah form untuk menambahkan data baru. Sedangkan jika menu “Refresh” dipilih, aplikasi akan mengunduh ulang data pada server REST API dan menampilkan ulang data terbaru ke dalam tabel.

Setelah code dijalankan akan muncul screen MainController yang menampilkan data dari server REST API.

Sekarang mari kita membuat code yang akan mengeksekusi code FormController untuk menambah data baru. Masukkan code berikut pada FormController :

 using Foundation;
 using System;
 using System.Collections.Generic;
 using UIKit;
 
 namespace silexiOS
 {
     public partial class FormController : UIViewController
     {
         private int EditId = 0;
         private string EditName;
         private double EditPrice;
 
         public FormController (IntPtr handle) : base (handle)
         {
         }
 
         public override void ViewDidLoad()
         {
             base.ViewDidLoad();
             this.InitializeBar();
             this.InitializeData();
         }
 
         private void InitializeBar()
         {
             List<UIBarButtonItem> rightButtons = new List<UIBarButtonItem>();
 
             rightButtons.Add(new UIBarButtonItem(
                 UIBarButtonSystemItem.Save,
                 (sender, e) => {
                     FoodMenu temp = new FoodMenu(txtName.Text, double.Parse(txtPrice.Text));
                     if (this.EditId == 0)
                         FoodLoader.InsertData(temp);
                     else
                         FoodLoader.UpdateData(temp, EditId);
                     NavigationController.PopViewController(true);
                 }
             ));
 
             if (this.EditId > 0)
             {
                 rightButtons.Add(new UIBarButtonItem(
                     UIBarButtonSystemItem.Trash,
                     (sender, e) =>
                     {
                         FoodLoader.DeleteData(this.EditId);
                         NavigationController.PopViewController(true);
                     }
                 ));
             }
 
             NavigationItem.RightBarButtonItems = rightButtons.ToArray();
         }
 
         private void InitializeData()
         {
             if (this.EditId > 0)
             {
                 this.txtName.Text = EditName;
                 this.txtPrice.Text = EditPrice.ToString();
             }
         }
 
         public void SetEdit(int edit_id, string name,double price)
         {
             this.EditId = edit_id;
             this.EditName = name;
             this.EditPrice = price;
         }
     }
 }

Yang terjadi pada baris code FormController adalah :

  • FormController selain digunakan untuk operasi insert data juga digunakan untuk operasi update dan delete data.
  • FormController memiliki variabel EditId, EditName dan EditPrice. Ketiga variabel tersebut digunakan untuk menyimpan nilai data yang akan di update/delete yaitu nilai id, name dan price -nya.
  • Jika nilai pada EditId bernilai 0 (nol) artinya FormController sedang menjalankan proses untuk input data, sedangkan jika nilai EditId bernilai lebih dari 0 (nol) artinya FormController sedang menjalankan fungsi untuk update data.
  • Ketika screen terbuka maka akan menjalankan fungsi “InitializeBar” dan “InitializeData”.
  • InitializeBar() akan menambahkan top bar menu yaitu menu “Save” (digunakan untuk menyimpan data yang sudah di input) dan menu “Trash” (digunakan untuk menghapus data yang telah dipilih). Menu “Trash” hanya ditampilkan jika FormController dalam mode update data (EditId bukan 0 nol) bukan insert data.
  • Jika tombol “Save” dipilih, maka terdapat dua opsi proses yang dijalankan. Opsi pertama jika nilai EditId == 0, maka  akan dijalankan proses insert data baru. Jika tidak maka akan dijalankan proses untuk memperbaharui data yang sudah ada sesuai dengan EditId yang ada.
  • InitializeData() akan dijalankan jika nilai EditId lebih dari 0 (nol) yang artinya FormController saat ini sedang dalam mode update data.

Saat aplikasi dijalankan, anda akan melihat list data yang berasal dari server REST API dan anda dapat menambahkan data baru untuk diinput pada server. Bagaimana dengan proses delete dan update? Agar dapat menjalankan proses Update dan Delete, masukkan fungsi berikut pada class “FoodAdapter” :

        public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
        {
            parent.EditPosition = indexPath.Row;
            parent.PerformSegue(editSegue, parent);
        }

Fungsi diatas akan mengeset nilai “EditPosition” pada MainController agar MainController dapat mengetahui data yang mana yang ingin diperaharui oleh user. Kemudian pada MainController perbaharui fungsi PrepareForSegue seperti berikut :

  public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
         {
             base.PrepareForSegue(segue, sender);
             if (segue.Identifier == "editSegue")
             {
                 var vc = segue.DestinationViewController as FormController;
                 if (EditPosition >= 0)
                 {
                     FoodMenu temp = this.datas[EditPosition];
                     vc.SetEdit(temp.Id, temp.Name, temp.Price);
                 }
             }
         }

Ketika proses segue dijalankan aplikasi akan memeriksa jika nilai “EditPosition” > 0 maka peralihan segue akan memanggil fungsi “SetEdit” yang pada FormController dimana guna dari fungsi tersebut adalah untuk melempar data FoodMenu yang ingin diperbaharui..

Selesai..

Silahkan jalankan program anda…

jan-01-2017-09-14-08

Silahkan lihat langsung code saya di https://github.com/sabithuraira/tutor_silex

Xamarin Android – Consuming REST API

Pada tulisan bulan Mei (lama kali ya..) saya pernah posting tulisan tentang cara membuat REST Web Service dengan Silex pada tautan berikut. Sejatinya saya berencana membuat tutorial cara mengkonsumsi Web Service tersebut di beberapa jenis platform seperti android dan iOS. Tapi karena keterbatasan waktu, saya baru sempat membuat penggunaannya pada platform android dengan menggunakan Xamarin. Berikut langkah-langkahnya :

  • Buatlah sebuah project android pada Xamarin.
  • Tambahkan package “Newtonsoft.Json” dan “Xamarin.Android.Support.v4” melalui nuget
  • Buatlah sebuah model dengan nama “FoodMenu.cs” yang akan mendefinisikan class data dari FoodMenu, masukkan code berikut :
using System;

namespace SilexSample
{
	public class FoodMenu
	{
		private int id;
		private string name;
		private double price;

		public FoodMenu (string name, double price)
		{
			this.name = name;
			this.price = price;
		}

		public FoodMenu (int id, string name, double price)
		{
			this.id = id;
			this.name = name;
			this.price = price;
		}

		public int Id{get{ return this.id; }}

		public string Name{get{ return this.name; }}
		public double Price{get{ return this.price; }}
	}
}
  • Tambahkan sebuah file yang digunakan untuk melakukan komunikasi dengan REST Server, beri nama dengan “FoodLoader.cs”. Pada file ini akan mendefinisikan fungsi untuk mengambil data, melakukan insert update serta menghapus data. Masukkan code berikut pada class FoodLoader :

 

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using Newtonsoft.Json.Linq;

namespace SilexSample
{
	public class FoodLoader
	{
		private static readonly string serverLink = "url_anda/web/index.php/";

		public FoodLoader ()
		{}

		public static List LoadData()
		{
			string link = serverLink + "daftar";
			List datas = new List ();
			try {
				HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
				request.Method="GET";
				request.ContentType = "application/json";

				HttpWebResponse response = (HttpWebResponse)request.GetResponse();

				if (response.StatusCode == HttpStatusCode.OK) {
					Stream receiveStream = response.GetResponseStream();
					StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
					string outputString = readStream.ReadToEnd();

					response.Close();
					readStream.Close();

					outputString = outputString.Trim().Replace("// ", "");
					var resultArray = JArray.Parse(outputString);

					for (int i=0; i < resultArray.Count; i++) {
						var jstock = (JObject)resultArray[i];

						FoodMenu temp_data=new FoodMenu(
							int.Parse((string)jstock["id"]),
							(string)jstock["name"],
							double.Parse((string)jstock["price"])
						);

						datas.Add(temp_data);
					}
				}
			}
			catch (Exception e) {
				throw;
			}
			return datas;
		}

		public static String InsertData(FoodMenu data)
		{
			string link = serverLink + "insert";
			String result = "";
			try {
				HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);

				StringBuilder postData = new StringBuilder();
				postData.Append("name="+data.Name+"&");
				postData.Append("price="+data.Price.ToString());
				var data_encode = Encoding.ASCII.GetBytes(postData.ToString());

				request.Method = "POST";
				request.ContentType = "application/x-www-form-urlencoded";
				request.ContentLength = data_encode.Length;

				using (var stream = request.GetRequestStream())
				{
					stream.Write(data_encode, 0, data_encode.Length);
				}

				var response = (HttpWebResponse)request.GetResponse();

				if (response.StatusCode == HttpStatusCode.OK) {
					Stream receiveStream = response.GetResponseStream();
					StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
					result = readStream.ReadToEnd();

					response.Close();
					readStream.Close();
				}
			}
			catch (Exception) {
				throw;
			}
			return result;
		}

		public static String UpdateData(FoodMenu data, int id)
		{
			string link = serverLink + "update/" + id.ToString ();
			String result = "";
			try {
				HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);

				StringBuilder postData = new StringBuilder();
				postData.Append("name="+data.Name+"&");
				postData.Append("price="+data.Price.ToString());
				var data_encode = Encoding.ASCII.GetBytes(postData.ToString());

				request.Method = "PUT";
				request.ContentType = "application/x-www-form-urlencoded";
				request.ContentLength = data_encode.Length;

				using (var stream = request.GetRequestStream())
				{
					stream.Write(data_encode, 0, data_encode.Length);
				}

				var response = (HttpWebResponse)request.GetResponse();

				if (response.StatusCode == HttpStatusCode.OK) {
					Stream receiveStream = response.GetResponseStream();
					StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
					result = readStream.ReadToEnd();

					response.Close();
					readStream.Close();
				}
			}
			catch (Exception) {
				throw;
			}
			return result;
		}

		public static String DeleteData(int id)
		{
			string link = serverLink + "delete/" + id.ToString ();
			String result = "";
			try {
				HttpWebRequest request = (HttpWebRequest)WebRequest.Create(link);
				request.Method = "DELETE";
				var response = (HttpWebResponse)request.GetResponse();

				if (response.StatusCode == HttpStatusCode.OK) {
					Stream receiveStream = response.GetResponseStream();
					StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);
					result = readStream.ReadToEnd();

					response.Close();
					readStream.Close();
				}
			}
			catch (Exception) {
				throw;
			}
			return result;
		}
	}
}
  • Sekarang kita akan membuat beberapa UI yang ditampilkan pada aplikasi android. UI yang akan dibuat antara lain UI main activity yang terdiri dari sebuah listview dan text, UI form melakukan input/update data, UI untuk menu pada bar, dan untuk row masing-masing data pada listview. Pertama buatlah UI untuk tampilan main pada Main.xml dengan memasukkan code berikut :

<?xml version=1.0 encoding=utf8?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
    android:orientation=vertical
    android:layout_width=match_parent
    android:layout_height=match_parent>
    <TextView
        android:text=List of Food
        android:layout_width=wrap_content
        android:layout_height=wrap_content
        android:id=@+id/title
        android:layout_gravity=center
        android:textSize=18dp
        android:paddingBottom=8dp />
    <ListView
        android:id=@+id/my_list
        android:layout_width=match_parent
        android:layout_height=match_parent
        android:paddingLeft=5dip
        android:paddingRight=5dip
        android:layout_margin=0dip
        android:padding=0dip
        android:divider=#00000000 />
</LinearLayout>

  • Setelah itu buat layout “form_food.xml” untuk form input data :

<?xml version=1.0 encoding=utf8?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
    android:orientation=vertical
    android:layout_width=match_parent
    android:layout_height=match_parent>
    <TextView
        android:text=List of Food
        android:layout_width=wrap_content
        android:layout_height=wrap_content
        android:id=@+id/title
        android:layout_gravity=center
        android:textSize=18dp
        android:paddingBottom=8dp />
    <ListView
        android:id=@+id/my_list
        android:layout_width=match_parent
        android:layout_height=match_parent
        android:paddingLeft=5dip
        android:paddingRight=5dip
        android:layout_margin=0dip
        android:padding=0dip
        android:divider=#00000000 />
</LinearLayout>

  • Lalu UI untuk tampilan row masing-masing data pada “row_food.xml :

<?xml version=1.0 encoding=utf8?>
<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
    android:layout_width=match_parent
    android:layout_height=40dp
    android:orientation=horizontal>
    <TextView
        android:text=Name 
        android:layout_width=wrap_content
        android:layout_height=wrap_content
        android:id=@+id/name
        android:paddingLeft=5dp
        android:layout_gravity=center
        android:width=250dp
        android:textSize=18dp />
    <TextView
        android:id=@+id/price
        android:layout_width=wrap_content
        android:layout_height=wrap_content
        android:gravity=right
        android:text=Price
        android:layout_gravity=center
        android:textSize=18dp />
</LinearLayout>

  • Terakhir UI untuk tampilan top bar pada aplikasi yang terdiri dari tombol Add dan Refresh data pada “bar_menu.xml”

<?xml version=1.0 encoding=utf8?>
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:tools=http://schemas.android.com/tools
    android:layout_width=fill_parent
    android:layout_height=wrap_content>
    <ImageButton
        android:id=@+id/add
        android:layout_width=50dip
        android:layout_height=fill_parent
        android:src=@mipmap/add
        style=?android:attr/borderlessButtonStyle
        android:layout_alignParentRight=true />
    <ImageButton
        android:id=@+id/refresh
        android:layout_width=50dip
        android:layout_height=fill_parent
        android:src=@mipmap/refresh
        style=?android:attr/borderlessButtonStyle
        android:layout_toLeftOf=@id/add />
</RelativeLayout>

  • Setelah membuat UI yang dibutuhkan, sekarang raciklah MainActivity pada aplikasi anda. Berikut code lengkap dari MainActivity :

 

using System;
using System.Collections.Generic;

using Android.App;
using Android.Widget;
using Android.OS;
using Android.Content;
using Android.Views;

namespace SilexSample
{
	[Activity (Label = "SilexSample", MainLauncher = true, Icon = "@mipmap/icon")]
	public class MainActivity : Activity
	{
		AlertDialog.Builder builder;
		private ListView my_list;
		private TextView title;
		private ImageButton btn_add;
		private ImageButton btn_refresh;

		private LoadTask asyncTask;  
		private Handler mHandler = new Handler();
		Runnable mUpdateTimeTask;

		List datas = new List ();
		private FoodAdapter adapter;
		private int current_position = 0;

		protected override void OnCreate (Bundle savedInstanceState)
		{
			base.OnCreate (savedInstanceState);

			SetContentView (Resource.Layout.Main);

			my_list = FindViewById (Resource.Id.my_list);
			title = FindViewById (Resource.Id.title);

			adapter = new FoodAdapter (this, this.datas);
			my_list.Adapter = adapter;
			ListEvent ();
			SetActionBar();

			mUpdateTimeTask = new Runnable(Run);
			mHandler.RemoveCallbacks(mUpdateTimeTask);
			mHandler.Post(mUpdateTimeTask);
		}

		private void RefreshData()
		{
			mUpdateTimeTask = new Runnable(Run);
			mHandler.RemoveCallbacks(mUpdateTimeTask);
			mHandler.Post(mUpdateTimeTask);
		}

		private void SetActionBar()
		{
			Context context = ActionBar.ThemedContext;

			ActionBar.DisplayOptions = ActionBarDisplayOptions.ShowCustom;
			ActionBar.SetCustomView(Resource.Layout.bar_menu);

			btn_add = FindViewById(Resource.Id.add);
			this.btn_add.Click += (o, e) =>
			{
				builder = new AlertDialog.Builder(this);
				builder.SetTitle("Add Data");

				View view_layout = LayoutInflater.From(this).Inflate(Resource.Layout.form_food, null);
				EditText name = view_layout.FindViewById(Resource.Id.name);
				EditText price = view_layout.FindViewById(Resource.Id.price);

				builder.SetView(view_layout);
				builder.SetPositiveButton("Ok", (os, es) =>
				{
					FoodMenu temp = new FoodMenu(name.Text, double.Parse(price.Text));
					FoodLoader.InsertData(temp);
					mHandler.RemoveCallbacks(mUpdateTimeTask);
					mHandler.Post(mUpdateTimeTask);
					((Dialog)os).Dismiss();
				});

				builder.SetNegativeButton("Cancel", (os, es) =>
				{
					((Dialog)os).Dismiss();
				});
				builder.Show();
			};

			btn_refresh = FindViewById(Resource.Id.refresh);
			btn_refresh.Click += (o, e) =>
			{
				RefreshData();
			};
		}

		private void ListEvent(){
			my_list.ItemClick += (o, e) => {
				this.current_position=e.Position;
				builder = new AlertDialog.Builder (this);
				builder.SetMessage("Select an action");
				builder.SetNegativeButton ("Delete", DeleteClicked);
				builder.SetPositiveButton("Update",UpdateClicked);
				builder.Show();
			};
		}

		private void DeleteClicked (object sender, DialogClickEventArgs e)
		{
			FoodLoader.DeleteData (datas [current_position].Id);
			mHandler.RemoveCallbacks(mUpdateTimeTask);
			mHandler.Post(mUpdateTimeTask);
			((Dialog)sender).Dismiss ();
		}
			
		private void UpdateClicked (object sender, DialogClickEventArgs e)
		{
			((Dialog)sender).Dismiss ();

			builder = new AlertDialog.Builder (this);
			builder.SetTitle ("Update Data");
			//
			View view_layout = LayoutInflater.From (this).Inflate (Resource.Layout.form_food, null);

			EditText name = view_layout.FindViewById (Resource.Id.name);
			EditText price = view_layout.FindViewById (Resource.Id.price);

			name.Text = datas [current_position].Name;
			price.Text = datas [current_position].Price.ToString ();

			//
			builder.SetView (view_layout);
			builder.SetPositiveButton ("Ok", (os, es) => {
				FoodMenu temp=new FoodMenu(name.Text,double.Parse(price.Text));
				FoodLoader.UpdateData(temp,datas[current_position].Id);
				mHandler.RemoveCallbacks(mUpdateTimeTask);
				mHandler.Post(mUpdateTimeTask);
				((Dialog)os).Dismiss ();
			});

			builder.SetNegativeButton ("Cancel", (os, es) => {
				((Dialog)os).Dismiss();
			});
			builder.Show();
		}

		public void Run(){
			if (asyncTask != null)
			if (asyncTask.GetStatus() == AsyncTask.Status.Running)
				asyncTask.Cancel(true);   		

			asyncTask = (LoadTask)new LoadTask (this).Execute ();
		}

		private class MyResult {
			public Boolean success;
			public Exception exception;
		}

		private class LoadTask : AsyncTask< Java.Lang.Void , Java.Lang.Void , MyResult>{
			MainActivity parent;
			public LoadTask(MainActivity parent){
				this.parent=parent;
			}

			protected override void OnPreExecute() {
				parent.title.Text = "Loading...";
				base.OnPreExecute();
			}

			protected override void OnPostExecute(MyResult result){
				if (result.exception != null && result.success == false)
					Toast.MakeText (parent, result.exception.Message, ToastLength.Long).Show ();

				parent.title.Text = "List of Food";
				base.OnPostExecute (result);
			}

			protected override MyResult RunInBackground(params Java.Lang.Void[] @params){			
				MyResult result = new MyResult();
				result.exception = null;
				result.success = true;

				try {
					parent.RunOnUiThread(() => {
						parent.LoadData();
					});
				}
				catch (Exception e) {
					result.exception = e;
					result.success = false;
				}
				return result;
			}
		}

		public void LoadData(){
			try {
				this.datas=FoodLoader.LoadData();
				adapter = new FoodAdapter (this, this.datas);
				my_list.Adapter = adapter;
			} 
			catch (Exception) {
				Toast.MakeText (this, "Something error on the server or your connection", ToastLength.Long).Show ();
			}
		}
	}
}

Setelah itu jalankan aplikasi anda untuk melihat hasilnya..

Anda juga dapat mengunduh aplikasi yang telah saya buat di https://github.com/sabithuraira/tutor_silex (aplikasi android yang dibuat saya letakkan pada folder “SilexAndroid”..

Happy coding..

Xamarin iOS – Use UIWebView

Ya, sebenarnya panduan untuk menggunakan UIWebView pada Xamarin iOS sudah ada di https://developer.xamarin.com/recipes/ios/content_controls/web_view/load_a_web_page/. Tetapi khusus iOS9 dan ke atas terdapat permasalahan munculnya blank page setiap kali load suatu website. Sebagai contoh jika kita ingin melihat tampilan “http://xamarin.com/&#8221; (bukan https), maka yang kita harapkan adalah tampilan seperti berikut :

simulator-screen-shot-nov-27-2016-11-30-02-pm

Bukan tampilan blank page seperti berikut :

screen-shot-2016-11-27-at-11-27-07-pm

Blank page di atas akan menampilkan pesan

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure.
Temporary exceptions can be configured via your app's Info.plist file.

Pada “Application Output”-nya. Untuk mengatasi hal tersebut anda harus memodifikasi Info.plist anda agar aplikasi mengizinkan mengakses http. Buka file Info.plist (lebih baik dibuka dengan editor biasa seperti atom/sublime text). Tambahkan konfigurasi berikut di dalam tag <dict> induk :

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSAllowsArbitraryLoads</key>
   <true/>
</dict>

Simpan perubahan tersebut lalu jalankan kembali aplikasi anda.. maka UIWebView sudah dapat menampilkan website dengan http kembali..

Happy coding…

React Native – ListView With Filter Header and Section Header

React Native – ListView With Filter Header and Section Header

Melanjutkan tulisan sebelumnya, kali ini kita akan mencoba menambahkan control untuk melakukan filter pada listview yang telah dibuat seperti gambar berikut :

screen-shot-2016-10-29-at-10-55-08-pm

Sebelum membuat sebuah screen seperti tampilan diatas, kita terlebih dahulu akan membuat sebuah screen yang terdiri dari ListView dan fitur untuk melakukan filter :

screen-shot-2016-10-26-at-3-43-04-pm

Untuk menambahkan component seperti gambar di atas, tambahkan header pada listview yang kita miliki dengan menambahkan code berikut pada code ListView :

         renderHeader={() =>
            <View style={styles.listview_header}>
              <TextInput style={styles.input}
                placeholder="Search..."
                onChangeText={(text) => console.log('searching for ', text)}
              />
            </View>
          }

Code di atas memasukkan code TextInput pada bagian header listview dimana jika diketikkan suatu karakter pada textinput tersebut, maka secara otomatis data pada listview akan difilter berdasarkan karakter yang kita ketikkan. Kok setelah diketik datanya masih belum ter-filter? Emang belum kita buat.. Untuk membuat fitur yang dapat melakukan filter data sesuai dengan data yang dimasukkan pengguna lakukan modifikasi code anda seperti berikut :

const data_array=[‘Music', ‘Movie', ‘Sport', 'Entertainment',];
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

export default class SampleMenu extends Component {
  constructor(props) {
    super(props);
    //definition of listview datasource
    this.state = {
      dataSource: ds.cloneWithRows(data_array),
      filter_string:'',
    };
  }

Rubah code pada bagian constructor seperti code di atas. Apa yang kita lakukan pada perubahan tersebut?

  1. Meletakkan data yang akan ditampilkan pada ListView di luar constructor/class. Kenapa ini dilakukan?
      • Jika data array diletakkan di dalam constuctor, kita tidak dapat menggunakan data array untuk di filter.
      • Antara data array dan dataSource harus dipisahkan sebab ketika proses filter berlangsung maka dataSource akan diperbaharui sesuai dengan data array yang sudah di filter.
  1. Nilai ds kita lettakkan di luar constructor/class. Kenapa?
      • Untuk memperbaharui nilai pada dataSource, kita akan menggunakan nilai ds. Jika diletakkan di constructor kita jadi tidak bisa menggunakan data ds tersebut.

Selain melakukan modifikasi code di atas, kita juga perlu menambahkan code yang akan mengeksekusi proses filter data ketika user memasukkan nilai untuk filter. Perbaharui nilai renderHeader anda seperti code berikut :

 renderHeader={() =>
            <View style={styles.listview_header}>
              <TextInput style={styles.input} placeholder="Search..."
                onChangeText={(text) =>{
                  var rows = [];
                  for (var i=0; i < data_array.length; i++) {
                     var stateName = data_array[i].toLowerCase();
                     if(stateName.search(text.toLowerCase()) !== -1){
                       rows.push(data_array[i]);
                     }
                   }
                   this.setState({dataSource:ds.cloneWithRows(rows)});
                }}
              />
            </View>
          }

Pada code di atas terdapat penambahan pada fungsi “onChangeText”. Code yang kita tambahkan adalah code yang melakukan pengecekan masing-masing data terhadap keyword untuk filter data. Setelah mendapatkan kumpulan data yang sudah sesuai dengan keyword, maka nilai dataSource akan diperbaharui kembali dengan data yang sudah ada. Setelah menambahkan code tersebut aplikasi yang kita buat akan melakukan filter sesuai keyword yang dimasukkan.

screen-shot-2016-10-26-at-11-20-13-pm

Section Header

ListView menyediakan fitur untuk melakukan grouping pada tampilan ListView anda. Hal ini sering digunakan untuk ListView yang memiliki data cukup banyak. Kita akan membuat ListView dengan section header dengan tampilan akhir seperti berikut :

screen-shot-2016-10-29-at-10-55-08-pm

Halaman tersebut menyediakan sebuah ListView dengan Section Header dan fitur filter pada bagian atas ListView. Baiklah, pertama-tama kita harus merubah jenis data yang ada pada ListView. Lakukan perubahan pada variabel data_array yang sebelumnya telah dibuat menjadi seperti berikut :

  const data_array=[];
  data_array['Sport']=['Soccer','Moto GP','Others'];
  data_array['IT & Technology']=['IT','Technology','Science'];
  data_array['Entertainment']=['Music','Movie','Art'];
  data_array['Interest']=['Travel','Style','Fashion','Business'];
  data_array['News & Info']=['Politics','World','Phenomenon'];
  data_array['Health']=['Health','Food','Lifestyle'];

Pada data sebelumnya aplikasi hanya menggunakan data array biasa sedangkan untuk kasus membuat ListView dengan section header data harus di-grouping di masing-masing data. Setelah itu pada constructor anda lakukan perubahan data pada variabel “dataSource” :

      dataSource: ds.cloneWithRowsAndSections(data_array),

Pada code ListView tepatnya code renderSeparator, lakukan perubahan berikut :

 renderSeparator={(sectionId, rowId) => <View key={sectionId+rowId} style={styles.separator_style} />}

Karena dilakukan perubahan pada struktur data_array, maka metode pada fitur filter harus kita modifikasi. Ganti code pada event “onChangeText” pada “renderHeader” menjadi seperti berikut :

                   var rows = [];
                   for (var key in data_array) {
                     if (!rows[key]) {
                       rows[key] = [];
                     }

                     for (var i=0; i < data_array[key].length; i++) {
                        var stateName = data_array[key][i].toLowerCase();
                        if(stateName.search(text.toLowerCase()) !== -1){
                          rows[key].push(data_array[key][i]);
                        }
                      }

                      if(rows[key].length==0)
                        delete rows[key];
                   }

                   this.setState({dataSource:ds.cloneWithRowsAndSections(rows)});

Yang terakhir tambahkan code renderSectionHeader pada ListView anda seperti berikut :

          renderSectionHeader={(rowData,sectionId)=>
            <View style={styles.sectionHeader}>
              <Text style={styles.sectionHeaderText}>{sectionId}</Text>
            </View>
          }

Silahkan jalankan aplikasi, jika code yang anda masukkan sudah benar maka akan tampil halaman seperti gambar aplikasi yang diinginkan.

Masih ga jalan?? Silahkan lihat project lengkap dari tulisan ini di : https://github.com/sabithuraira/react_native_playground. Untuk code tulisan ini bisa dicek langsung di https://github.com/sabithuraira/react_native_playground/blob/master/application/components/SampleMenu.js

Happy coding..

React Native – Using ListView

React Native – Using ListView

Untuk menampilkan sekumpulan data kepada pengguna, aplikasi biasanya menggunakan tabel. Untuk aplikasi android sering digunakan ListView, iOS menggunakan UITableView dan pada react native kita akan menggunakan ListView. Kita akan coba membuat sebuah tampilan seperti pada gambar berikut :

screen-shot-2016-10-26-at-1-59-45-pm

Pertama-tama buatlah sebuah componen dengan code listview di dalamnya seperti berikut :

import React, { Component } from 'react';
import {
  StyleSheet, Text, View, ListView, TouchableHighlight,
} from 'react-native';

export default class SampleMenu extends Component {
  constructor(props) {
    super(props);
    //definition of listview datasource
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows([
        'Music', 'Movie', 'Sport', 'Entertainment',
      ])
    };
  }

  render() {
    return (
      <View style={{flex: 1, paddingTop: 22,marginTop:45}}>
        <ListView style={styles.listview_style}
          dataSource={this.state.dataSource}
          renderRow={(rowData,sectionId,rowId) =>(
              <View style={styles.row}>
                <Text style={styles.row_style}>{rowData}</Text>
              </View>
          )}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  listview_style: {
    padding: 10,
    backgroundColor: '#F6F6F6',
  },
  row: {
    flexDirection: 'row',
    backgroundColor: 'white',
  },
  row_style: {
    flex: 1,
    fontSize: 15,
    textAlign: 'left',
    margin: 10,
  },
  separator_style: {
   flex: 1,
   height: StyleSheet.hairlineWidth,
   backgroundColor: '#8E8E8E',
  },
});

Code di atas akan menghasilkan interface seperti berikut :

screen-shot-2016-10-26-at-2-24-00-pm

Agar lebih enak dilihat tambahkan pembatas/separator pada setiap baris.

renderSeparator={(sectionId, rowId) => <View key={rowId} style={styles.separator_style} />}

Tambahkan code tersebut dibawah tag code “renderRow” pada ListView, maka tampilan listview akan berubah menjadi seperti berikut :

screen-shot-2016-10-26-at-2-31-40-pm

Selain menampilkan data, anda juga dapat menambahkan sebuah perintah yang dijalankan ketika pengguna memilih salah satu data. Pada contoh ini kita akan menampilkan sebuah pesan jika pengguna memilih salah satu data.

              <View style={styles.row}>
                <Text style={styles.row_style}>{rowData}</Text>
              </View>

Rubah code di atas menjadi seperti berikut :

            <TouchableHighlight onPress={() => {
                Alert.alert(
                  'Enter title here..',
                  'You click on '+rowData,
                );
            }}>
              <View style={styles.row}>
                <Text style={styles.row_style}>{rowData}</Text>
              </View>
            </TouchableHighlight>

Untuk memberikan respon ketika pengguna menekan suatu data, kita harus menggunakan component “TouchableHighlight”. Setelah menambahkan code tersebut maka ketika pengguna menekan salah satu data akan dijalankan fungsi “onPress”. Pada contoh ini aplikasi yang kita buat akan menampilkan pesan setiap kali pengguna menekan salah satu data.

screen-shot-2016-10-26-at-3-08-06-pm

Selesai, itulah contoh pengembangan aplikasi react native dengan listview sederhana..

Happy coding..

%d bloggers like this: