วิธีการ Save และ Query ParseObject

วิธีการ Save และ Query ParseObject Cover Image

หลังจากบทความที่แล้ว ทำความรู้จักกับ Parse.com ได้เขียนแนะนำน การใช้งาน Parse ในเบื้องต้นกันไปแล้ว ในบทนี้ความนี้เลยจะมาพูดถึง วิธีการ Query ข้อมูลจาก Parse กันครับ

อยากที่ได้พูดไปในบทความที่แล้ว Parse นั้นจะมี Parse Android SDK ให้เราได้ดาวน์โหลดไป เมื่อทำการติดตั้ง ก็จะมีให้ใส่ APPID และ CLIENTID สำหรับเพื่อระบุตัวตน ว่าอันนี้เป็นแอพของๆเรา ที่ได้ทำการสร้างในเว็บ Parse ไว้ ทีนี้เมื่อเราใส่ APPID และ CLIENTID ก็จะสามารถเข้าถึงข้อมูลจาก Parse ได้

บทความนี้พูดถึง Parse บน Platform Android นะครับ

ParseObject

ปกติเราจะเก็บข้อมูลไปที่ Parse นั้น เราต้องเก็บอยู่ในรูป ParseObject ครับโดย ParseObject นั้นจะเก็บข้อมูลเป็นแบบ Key-Value คล้ายๆกับการเก็บแบบ HashMap แล้วก็สามารถส่งค่าร่วมกับ JSON ได้อีกด้วย โดยข้อมูลที่ ParseObject สามารถเก็บได้นั้นมีทั้ง String, integer, Boolean, float, Array หรือแม้แต่ ParseObject เอง ส่วนตัว ParseObject นั้นก็จะมีชื่อในตัวมันเอง เอาไว้สำหรับให้รู้ว่ามันคืออะไร อย่างเช่น ParseObject ชื่อ Student ก็เก็บข้อมูลนักเรียน, ParseObject ชื่อ Location ก็อาจเก็บข้อมูลสถานที่ต่าง เป็นต้น

Save ParseObject

ตัวอย่างข้างล่างนี้ คือการสร้าง ParseObject ของ นักฟุตบอล และ Save ข้อมูลไปที่ Parse เช่น

ParseObject player = new ParseObject("Player");
player.put("name", "Cristiano Ronaldo");
player.put("age", 29);
player.put("club", "Real Madrid");
player.put("nation", "Portugal");
player.put("retired", false);
player.saveInBackground();

จากด้านบน ผมทำการสร้างนักฟุตบอล ชื่อว่า Cristiano Ronaldo ขึ้นมา โดยมีทั้งอายุ สโมสร ทีมชาติที่เล่น แขวนสตั๊ดหรือยัง เป็นต้น จากนั้นก็สั่ง saveInBackground(); คือทำการเซฟข้อมูลนี้ไปยัง Parse จากข้อมูลด้านบน เวลาเก็บข้อมูลลง Parse ก็เหมือนกับการบันทึกเป็น 1 record สำหรับ Database ทั่วๆไป ประมาณว่า บันทึกคนนี้ ไว้ที่ Table Player

หากผมเพิ่มนักฟุตบอล ไปอีกคนนึง ดังนี้

ParseObject player = new ParseObject("Player");
player.put("name", "Diego Maradona");
player.put("age", 53);
player.put("club", "Boca Juniors");
player.put("nation", "Argentina");
player.put("retired", true);
player.saveInBackground();

ข้อมูลก็จะถูกส่งไปเก็บที่ Parse อีก 1 record ทำให้ตอนนี้ ParseObject ที่ชื่อ Player มีข้อมูลทั้งหมด 2 records. หากมาดูที่ Dashboard ของ Parse ก็จะได้ข้อมูลดังนี้

ParseObject in Data Browser

สังเกต มี objectId , updatedAt และ createdAt ด้วย ปกติแล้ว Parse จะ auto มาให้ด้วย แสดงถึงเวลาที่ Object นี้สร้างเมื่อไหร่ และอัพเดทเมื่อไหร่ และมี objectId ที่ไม่ให้ซ้ำกับ Object อื่นๆ (ถ้าปกติ Database ทั่วไป ก็เปรียบเสมือน Primary Key นั่นเอง )

Retreive ParseObject

หากเราอยาก get ข้อมูลจาก Parse ให้มาแสดงบนแอพของเราละ ทำได้ยังไง? Parse นั้นก็มี API เตรียมมาให้เราแล้วครับ โดยหากเรารู้ objectId น้นๆ เราก็สามารถ get ข้อมูลได้ด้วยคำสั่งนี้

ParseQuery<ParseObject> query = ParseQuery.getQuery("Player");
query.getInBackground("x12345678", new GetCallback<ParseObject>() {
  public void done(ParseObject object, ParseException e) {
    if (e == null) {
      // Bingo!
    } else {
      // check errors.
    }
  }
});

จากด้านบนเป็นการใช้ ParseQuery ทำการ query ข้อมูล ParseObject ที่ชื่อ Player โดยหาข้อมูลที่มี objectId = x12345678 (อันนี้ หากเป็น SQL ที่เราคุ้นเคยกัน ก็เปรียบเสมือน Select * from Player where objectId = x12345678)

ค่านั้นจะถูก callback กลับมาที่ GetCallback#done() โดยเมธอด done นั้นส่งข้อมูลกลับมาให้เราคือ ParseObject ที่เราต้องการ กับ ParseException ก็คือมี Exception อะไรรึเปล่า ปกติ ก็ต้องทำการเช็คก่อน ถ้าไม่มี Exception เราก็จะสามารถเข้าถึงข้อมูลได้

String name = object.getString("name");
int age = object.getInt("age");
String club = object.getString("club");
String nation = object.getString("nation");
boolean isRetired = object.getBoolean("retired");

ข้างบน เราก็สามารถที่จะ get เอาข้อมูลได้แล้ว นอกเหนือจากข้อมูลที่เราระบุ Key ไป เรายังสามารถ get ข้อมูลที่ Parse นั้น Auto มาให้ได้อีก

String objectId = object.getObjectId();
Date updatedAt = object.getUpdatedAt();
Date createdAt = object.getCreatedAt();

Update ParseObject

การอัพเดทข้อมูลของ ParseObject ทำคล้ายๆกับการ Query ข้อมูลจาก ParseObject ครับ โดยเราก็ระบุ objectId ที่ต้องการ แล้วก็ทำการ query ด้วยคำสั่งนี้

ParseQuery<ParseObject> query = ParseQuery.getQuery("Player");
query.getInBackground("x12345678", new GetCallback<ParseObject>() {
  public void done(ParseObject object, ParseException e) {
    if (e == null) {
      // Bingo!
    } else {
      // check errors.
    }
  }
});

แทนที่เราจะ ทำการ get ค่าหลักจาก callback แล้ว เราก็ทำการ put ค่าไปใหม่ เช่น

ParseQuery<ParseObject> query = ParseQuery.getQuery("Player");
query.getInBackground("x12345678", new GetCallback<ParseObject>() {
  public void done(ParseObject object, ParseException e) {
    if (e == null) {
      object.put("age", 30);
      object.put("club", "Barcelona");
      object.saveInBackground();
    } else {
      // check errors.
    }
  }
});

ด้านบน เป็นการ update อายุ เปลี่ยนเป็นอายุ 30 และเปลีย่นสโมสรเป็น Barcelona

หากต้องการ update ค่าที่เป็นจำนวน เราสามารถใช้ object.increment("age"); เพื่อทำการเพิ่มจำนวนทีละ 1 หรือหากต้องการเพิ่มทีละ 5 ก็สามารถ ใช้ object.increment("age", 5); แบบนี้

Delete ParseObject

การลบข้อมูล ParseObject ก็ทำได้เช่นเดียวกับการ query และการ update คือ ต้องทำการ Query Object นั้นๆมาก่อน โดยระบุ objectId มา เมื่อ get ข้อมูลมาได้ ก็สั่ง object.deleteInBackground(); แบบนี้

ParseQuery<ParseObject> query = ParseQuery.getQuery("Player");
query.getInBackground("x12345678", new GetCallback<ParseObject>() {
  public void done(ParseObject object, ParseException e) {
    if (e == null) {
      object.deleteInBackground();
    } else {
      // check errors.
    }
  }
});

การใช้งานเบื้องต้นของ ParseObject ก็เอาเพียงเท่านี้ก่อนครับ จะเห็นว่าในส่วนการ Query ข้อมูลนั้นเรายังต้องทำการระบุ objectId ด้วยทุกครั้งเลยหรอ ? หากจะ Query แบบอื่นละ ทำได้มั้ย ? ขอตอบว่าทำได้ครับ แต่บทความนี้ ขอพูดถึงเฉพาะพื้นฐานก่อนครับ ไว้ค่อยต่อ บทความหน้า

ตัวอย่าง Application with ParseObject

มาถึงการลองทำแอพตัวอย่างการใช้ ParseObject บนแอนดรอยส์กันเลยครับ ผมเข้าใจว่าผู้อ่านทุกคน สามารถสร้าง Parse Starter App ได้ทุกคนนะครับ หากใครสร้างไม่เป็น อ่านได้จากบทความนี้ ทำความรู้จักกับ Parse.com

เอาละ สมมติว่าทุกคนจัดการตั้งค่า APP_ID และ CLIENT_ID รวมถึง initialize Parse เรียบร้อยแล้วนะครับ

เริ่มแรก ผมทำการสร้าง Layout ไว้สำหรับ ใส่ข้อมูลครับ แก้ไขจากไฟล์ activity_main.xml

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="@dimen/activity_vertical_margin"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ทำการเพิ่มรายชื่อนักฟุตบอล"
        android:textSize="20sp"
        android:layout_gravity="center"
        android:id="@+id/text_title"
        android:layout_marginBottom="32dp"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPersonName"
        android:hint="ใส่ชื่อนักฟุตบอล"
        android:ems="10"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_name"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:hint="ใส่อายุ"
        android:ems="10"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_age"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="ใส่ชื่อสโมสร"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_club"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ใส่ชื่อทีมชาติ"
        android:ems="10"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_nation"/>
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="แขวนสตั๊ด"
        android:layout_gravity="center"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_retired"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="บันทึก"
        android:id="@+id/add_button_save"
        android:layout_gravity="center_horizontal"/>

    <LinearLayout android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="horizontal"
                  android:gravity="center"
                  android:padding="@dimen/activity_horizontal_margin"
                  android:background="#ff007767"
                  android:layout_weight="1">

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.6"
            android:id="@+id/add_object_id"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.4"
            android:text="Query"
            android:id="@+id/add_button_query"/>


    </LinearLayout>

</LinearLayout>

Layout ผมทำแบบ Simple เลยนะครับ คือมีให้กรอกข้อมูล แล้วก็มีปุ่ม Toggle สำหรับ ตั้ค่า ON/OFF เลือกว่าแขวนสตั๊ดแล้วหรือยัง จากนั้นก็เป็นปุ่ม บันทึก ถัดมาข้างล่าง ใส่แถบสีไว้แบ่งแยกนิดหน่อย อันนี้เอาไว้เวลา Query ข้อมูลนะครับ ต้องใส่ objectId แล้วก็กด Query (พอดีขี้เกียจนั่งทำ Layout หลายๆ หน้า ทำรวมๆกันไปก่อน ให้พอเห็นภาพ)

Layout Parse

มาดูที่ Activity ครับ เปิด ParseStarterProjectActivity.java

ขั้นแรก ผมทำการเชื่อม View ที่ประกาศไว้ใน Layout กับในคลาส Java เข้าด้วยกันครับ ที่เมธอด onCreate()

final EditText playerName =
        (EditText) findViewById(R.id.add_player_name);
final EditText playerAge = (EditText) findViewById(R.id.add_player_age);
final EditText playerClub =
        (EditText) findViewById(R.id.add_player_club);
final EditText playerNation =
        (EditText) findViewById(R.id.add_player_nation);
final ToggleButton playerIsRetired =
        (ToggleButton) findViewById(R.id.add_player_retired);
Button buttonSave = (Button) findViewById(R.id.add_button_save);

final EditText objectId = (EditText) findViewById(R.id.add_object_id);
Button buttonQuery = (Button) findViewById(R.id.add_button_query);

ส่วนปุ่ม บันทึก ก็ทำการ setOnClikcListener() ให้มัน โดยเมื่อทำการคลิ๊กปุ่มนี้ ก็จะเป็นการบันทึกข้อมูลจากEditText ที่กรอกไว้ บันทึกไปยัง Parse แบบนี้

buttonSave.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ParseObject player = new ParseObject("Player");
        player.put("name", playerName.getText().toString());
        player.put("age", Integer.parseInt(
                playerAge.getText().toString()));
        player.put("club", playerClub.getText().toString());
        player.put("nation", playerNation.getText().toString());
        player.put("retired", playerIsRetired.isChecked());

        player.saveInBackground(new SaveCallback() {
            @Override
            public void done(ParseException e) {
                if (e == null) {
                    // Save success!
                    playerName.setText("");
                    playerAge.setText("");
                    playerClub.setText("");
                    playerNation.setText("");
                    playerIsRetired.setChecked(false);
                } else {
                    // some errors!
                    Log.e("Parse Error", e.toString());
                }
            }
        });

    }
});

อ้อ ปกติการเซฟข้อมูลไป Parse เราจะใช้แค่ saveInBackground() โดยไม่มี parameter อะไรส่งไปด้วย แต่ตัวอย่างนี้ เราสามารถส่ง SavCallback ไปได้ เพื่อเช็คว่ามันเซฟได้หรือไม่ได้ มี error หรือไม่ ก็ตามโค๊ด return กลับมาเมื่อเซฟเสร็จ พร้อมกับส่ง ParseException กลับมา หาก เป็น null ก็แสดงว่าเซฟได้ด้วยดี ไม่มีปัญหา หาก ParseException มีค่า ก็เช็คดูว่า error เพราะอะไร

Insert Data to Parse

ตัวอย่างในรูป ผมทำการกรอกข้อมูลลงไป จากนั้นกด บันทึก พอไปดูข้อมูลใน Dashboard จะได้ข้อมูลลักษณะนี้

Teerasil in Data Browser

ต่อมาหากผมต้องการจะ Query ข้อมูลจาก Parse มา จะทำยังไง? ก็เลยทำการให้ ปุ่ม Query ที่ทำการสร้างไว้ setOnClickListener() ให้มันซะ

buttonQuery.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ParseQuery<ParseObject> query = ParseQuery.getQuery("Player");
        query.getInBackground(objectId.getText().toString(),
                new GetCallback<ParseObject>() {
                    public void done(ParseObject object, ParseException e) {
                        if (e == null) {
                            String name = object.getString("name");
                            int age = object.getInt("age");
                            String club = object.getString("club");
                            String nation = object.getString("nation");
                            boolean isRetired =
                                    object.getBoolean("retired");

                            Toast.makeText(getApplicationContext(),
                                    "Name: " + name + " Age: " + age +
                                            " Club: " +
                                            club + " Nation: " +
                                            nation +
                                            " isRetired:" + isRetired,
                                    Toast.LENGTH_LONG
                            ).show();
                        } else {
                            // check errors.
                            Log.e("Query Error: ", e.toString());
                        }
                    }
                }
        );
    }
});

จากโค๊ดด้านบน คือเมื่อทำการกดปุ่ม Query มันก็จะไป get ข้อมูลจาก Parse โดยเราระบุ objectId ที่เห็นจาก Data Browser ครับ หากใส่ objectId ได้ถูก ก็จะมี Toast ขึ้นมาแสดงข้อมูลที่ได้ครับ หากมี Error ก็จะมี Log โชว์ว่า error เพราะอะไร

Query Data

ทีนี้ผมลองเพิ่มข้อมูลอีก แล้วกลับไปลองเช็คที่ Data Browser ใหม่อีกครั้ง พบว่า มีข้อมูลเพิ่มขึ้นแล้ว

Data Browser finished

ตัวอย่างการใช้งาน Parse คร่าวๆ ก็มีเพียงเท่านี้ครับ ลองทดลองเพิ่มข้อมูล ทดลอง get ข้อมูลแต่ละ objectId ดูครับ รายละเอียดเพิ่มเติม อ่านได้ที่ Docs ของ Parse เองเลยครับ มีข้อมูลที่ละเอียดพอสมควร

Data Browser จริงๆ เราก็สามารถที่จะสร้าง ParseObject ผ่าน Data Browser ได้เหมือนกันครับ ยังไง ก็ลองๆเล่นดูครับ บทความหน้าผมจะพูดถึงการ Query หลายๆแบบกันครับ ไม่ใช่เฉพาะต้องรู้ objectId เท่านั้น

โค๊ดทั้งหมด

activity_main.xml

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

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:padding="@dimen/activity_vertical_margin"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ทำการเพิ่มรายชื่อนักฟุตบอล"
        android:textSize="20sp"
        android:layout_gravity="center"
        android:id="@+id/text_title"
        android:layout_marginBottom="32dp"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPersonName"
        android:hint="ใส่ชื่อนักฟุตบอล"
        android:ems="10"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_name"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:hint="ใส่อายุ"
        android:ems="10"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_age"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="ใส่ชื่อสโมสร"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_club"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ใส่ชื่อทีมชาติ"
        android:ems="10"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_nation"/>
    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="แขวนสตั๊ด"
        android:layout_gravity="center"
        android:layout_marginBottom="16dp"
        android:id="@+id/add_player_retired"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="บันทึก"
        android:id="@+id/add_button_save"
        android:layout_gravity="center_horizontal"/>

    <LinearLayout android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="horizontal"
                  android:gravity="center"
                  android:padding="@dimen/activity_horizontal_margin"
                  android:background="#ff007767"
                  android:layout_weight="1">

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.6"
            android:id="@+id/add_object_id"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.4"
            android:text="Query"
            android:id="@+id/add_button_query"/>


    </LinearLayout>

</LinearLayout>

ไฟล์ ParseStarterProjectActivity.java

package com.devahoy.parse.demo;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.parse.GetCallback;
import com.parse.ParseAnalytics;
import com.parse.ParseException;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import com.parse.SaveCallback;

public class ParseStarterProjectActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final EditText playerName =
                (EditText) findViewById(R.id.add_player_name);
        final EditText playerAge = (EditText) findViewById(R.id.add_player_age);
        final EditText playerClub =
                (EditText) findViewById(R.id.add_player_club);
        final EditText playerNation =
                (EditText) findViewById(R.id.add_player_nation);
        final ToggleButton playerIsRetired =
                (ToggleButton) findViewById(R.id.add_player_retired);
        Button buttonSave = (Button) findViewById(R.id.add_button_save);

        final EditText objectId = (EditText) findViewById(R.id.add_object_id);
        Button buttonQuery = (Button) findViewById(R.id.add_button_query);

        buttonSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ParseObject player = new ParseObject("Player");
                player.put("name", playerName.getText().toString());
                player.put("age", Integer.parseInt(
                        playerAge.getText().toString()));
                player.put("club", playerClub.getText().toString());
                player.put("nation", playerNation.getText().toString());
                player.put("retired", playerIsRetired.isChecked());

                player.saveInBackground(new SaveCallback() {
                    @Override
                    public void done(ParseException e) {
                        if (e == null) {
                            // Save success!
                            playerName.setText("");
                            playerAge.setText("");
                            playerClub.setText("");
                            playerNation.setText("");
                            playerIsRetired.setChecked(false);
                        } else {
                            // some errors!
                            Log.e("Parse Error", e.toString());
                        }
                    }
                });

            }
        });

        buttonQuery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ParseQuery<ParseObject> query = ParseQuery.getQuery("Player");
                query.getInBackground(objectId.getText().toString(),
                        new GetCallback<ParseObject>() {
                            public void done(ParseObject object, ParseException e) {
                                if (e == null) {
                                    String name = object.getString("name");
                                    int age = object.getInt("age");
                                    String club = object.getString("club");
                                    String nation = object.getString("nation");
                                    boolean isRetired =
                                            object.getBoolean("retired");

                                    Toast.makeText(getApplicationContext(),
                                            "Name: " + name + " Age: " + age +
                                                    " Club: " +
                                                    club + " Nation: " +
                                                    nation +
                                                    " isRetired:" + isRetired,
                                            Toast.LENGTH_LONG
                                    ).show();
                                } else {
                                    // check errors.
                                    Log.e("Query Error: ", e.toString());
                                }
                            }
                        }
                );
            }
        });

        ParseAnalytics.trackAppOpened(getIntent());
    }

}


Reference:

Chai

Chai Phonbopit : Developer แห่งหนึ่ง • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Node.js, JavaScript และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด