2019年6月13日 星期四

 Google App Engine(三)

延續上一章節:

本章運用到了: MVC設計模式 Adapter接配線模式 BaseAdapter ListView的 ViewHolider概念

本章目的:
1. 與App進行互動 2. 完成『查詢』功能 3.『新增』、『刪除』、『變更』完全互動請至→Google App Engine(四)


將資料轉換成JSON

簡述:如何將後端的資料 轉換成能與APP溝通的模式,就是轉為JSON or XML

(A) 用途目的:方便App取得資料,首先添加 lib


(B) 撰寫程式


JAVA:MyBook
package com.example.myapplication.backend.po;

 public class MyBook {
   private long key;
   private String title;
   private String author;
   private int price;
   private long time;

 //設定好需要的變數後,剩下的用getter setter產生

    public long getKey() {
       return key;
   }
    public void setKey(long key) {
       this.key = key;
   }
    public String getTitle() {
       return title;
   }
    public void setTitle(String title) {
       this.title = title;
   }
    public String getAuthor() {
       return author;
   }
    public void setAuthor(String author) {
       this.author = author;
   }
    public int getPrice() {
       return price;
   }
    public void setPrice(int price) {
       this.price = price;
   }
    public long getTime() {
       return time;
   }
    public void setTime(long time) {
       this.time = time;
   }
}


JAVA:QueryServlet
package com.example.myapplication.backend;

import com.example..myapplication.backend.po.MyBook;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Query;
import com.google.gson.Gson;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

 public class QueryServlet extends HttpServlet {

    @Override
   public void doGet(HttpServletRequest req, HttpServletResponse resp)
           throws IOException {

        resp.setContentType("text/plain");

        DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

        //查詢 資料表
       Query q = new Query("Book");

         //想辦法把取得的資料 裝進陣列中

        List<mybook> list =new ArrayList<>();

        for (Entity entity : ds.prepare(q).asIterable()) {
           //封裝資料
           MyBook myBook = new MyBook();
           myBook.setKey(entity.getKey().getId());
           myBook.setTitle(entity.getProperty("title").toString());
           myBook.setAuthor(entity.getProperty("author").toString());
           myBook.setPrice(Integer.parseInt(entity.getProperty("price").toString()));
           myBook.setTime(Long.parseLong(entity.getProperty("time").toString()));

            list.add(myBook);
       }

        Gson gson = new Gson();

        String json = gson.toJson(list);

        resp.getWriter().println(json);

        //我們需要把資料轉換成GSON的格式,方便APP下載
       Runnable r = new Runnable() {
           @Override
           public void run() {

            }
       };
   }
}


查看網頁是否成功(注意網址最後為 /query)
"你的專案名稱".appspot.com/query

成功如下圖(這是用GsonFormat呈現的)




App基本設定

簡述:後端的資料都處理好了,開始為前端做準備

Step1. Android Studio→先進行環境設定 加入網路權限
       詳細位置:Android Manifest
        程式碼:<uses-permission android:name="android.permission.INTERNET" />


Step2. Android Studio→先進行環境設定,並加入第三方程式庫

compile 'com.google.code.gson:gson:2.2.4'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.squareup.okhttp:okhttp:2.0.0'
詳細位置:設定→Plugins→搜尋"gsonformat"
使用介紹:能自動產生 對應到該頁面json格式 的類別


Step3. Android Studio→創造類別、Layout

Class


JsonAdapter (因為json沒有Adapter,所以我們要自己做一個Adapter)
Layout
Item.xml (用來顯示每筆資料用的item)
model
Servive_My_Book(管理資料,解除耦合)
po
MyBook(用來對應到網路上json格式用), po這個資料夾命名 代表永續儲存

 Step4. 啟用google的擴充功能,使得網頁上Json的格式更清晰

             
工具都準備好了 我們開始來實現這個功能吧






App下載JSON資料
Step1 Layout
Layout:activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:background="@android:color/holo_green_light"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:paddingBottom="5dp"
  android:orientation="vertical"
  android:paddingLeft="5dp"
  android:paddingRight="5dp"
  android:paddingTop="5dp"
  tools:context="com.example.app_cloud.MainActivity">
 
  <ListView
      android:layout_marginTop="20dp"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_weight="2"
      android:id="@+id/listView"
      android:layout_alignBottom="@+id/webView"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true" />


</LinearLayout>


Layout:item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@android:color/darker_gray"
  android:orientation="vertical"
  android:weightSum="1">


  <TextView
      android:id="@+id/tv_key"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_margin="5dp"
      android:layout_weight="0.06"
      android:text="key"
      android:textAppearance="?android:attr/textAppearanceLarge" />


  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="horizontal">


      <TextView


          android:id="@+id/tv_title"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_margin="5dp"
          android:text="title"
          android:textSize="10dp" />


      <TextView
          android:id="@+id/tv_author"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_margin="5dp"
          android:layout_weight="0.06"
          android:text="author"
          android:textSize="10dp" />


  </LinearLayout>


  <LinearLayout
      android:orientation="horizontal"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">


      <TextView
          android:id="@+id/tv_price"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_margin="5dp"
          android:text="price"
          android:textSize="20dp" />


  </LinearLayout>
  <TextView
      android:id="@+id/tv_time"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_margin="5dp"
      android:text="time"
      android:textSize="10dp" />
  <View
      android:layout_width="match_parent"
      android:layout_height="10dp"
      android:background="@android:color/holo_green_light"/>


</LinearLayout>



Step2 MyBook:建立對應到網路上 對應的類別,之後打勾即可



JAVA :MyBook
package com.example.app_cloud.po;

 public class MyBook {
   private long key;
   private String title;
   private String author;
   private int price;
   private long time;

  //這邊是要給 ArrayAdapter用的

 @Override
public String toString() {
   SimpleDateFormat f = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss E");

   String data = String.format
   ("書名:%s\tkey:%d\n作者:%s\t價格:%d\n上架時間:%s"
   , title, key, author, price, f.format(new Date(time)));
   return data;
}

   public long getKey() {
       return key;
   }
    public void setKey(long key) {
       this.key = key;
   }
    public String getTitle() {
       return title;
   }
    public void setTitle(String title) {
       this.title = title;
   }

    public String getAuthor() {
       return author;
   }
    public void setAuthor(String author) {
       this.author = author;
   }
    public int getPrice() {
       return price;
   }
    public void setPrice(int price) {
       this.price = price;
   }
    public long getTime() {
       return time;
   }
    public void setTime(long time) {
       this.time = time;
   }
}


Step3 Service_My_Book:(model)

JAVA :Service_My_Book
package com.example.app_cloud.model;

 import com.example.app_cloud.po.MyBook;
import java.text.SimpleDateFormat;
import java.util.Date;

 public class Service_My_Book {

    //JSON中每一筆資料都是 MyBook,所以用型別是MyBook 集合去裝
   private MyBook[] service_myBooks;
  
   //建構子
   public Service_My_Book(MyBook[] myBooks) {
       this.service_myBooks = myBooks;
   }

    //需要資料就透過 Service_My_Book,不要伸手到Mybook中拿
   public String getKey(int postion) {
       return "書籤代碼= " + String.valueOf(service_myBooks[postion].getKey());
   }

    public String getTitle(int postion) {
       return "標題= " + service_myBooks[postion].getTitle();
   }

    public String getAuthor(int postion) {
       return "作者名稱= " + service_myBooks[postion].getAuthor();
   }

    public String getPrice(int postion) {
       return "販售的金額= " 
              + String.valueOf(service_myBooks[postion].getPrice());
   }

    public String getTime(int postion) {
//創立一個format的物件
       SimpleDateFormat formatdate; 
       formatdate= new SimpleDateFormat("yyyy/MM/dd HH:mm:ss E");

 //先取得時間
       Long time = service_myBooks[postion].getTime();

        String data = String.format(formatdate.format(new Date(time)));

        return "上架時間= " + data;
   }
}


Step4 JsonAdapter:(Adapter)

JAVA :JasonAdapter
package com.example.app_cloud;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.app_cloud.model.Service_My_Book;
import com.example.app_cloud.myapp.MyApp;
import com.example.app_cloud.po.MyBook;

 public class JsonAdapter extends BaseAdapter {
   private Context context;
   private MyBook[] myBooks;
   private LayoutInflater layoutInflater;

    //建構子
   public JsonAdapter(Context context, MyBook[] myBooks) {
       this.context = context;
       this.myBooks = myBooks;
   }
    //size
   @Override
   public int getCount() {
       return myBooks.length;
   }
//item
   @Override
   public Object getItem(int i) {
       return myBooks[i];
   }
    //id
   @Override
   public long getItemId(int i) {
       return i;
   }

    @Override
   public View getView(int postion, View view, ViewGroup viewGroup) {
       ViewHolder viewHolder;

        //view == null
       if (view == null) {

            //如果view == null,我才創建一個新的view
            //避免下載一次 就創造一個View出來
           view = layoutInflater.from(context).inflate(R.layout.item, null);

            //如果view == null,我再findViewByid
           viewHolder = new ViewHolder();

        viewHolder.m_tv_time = (TextView) view.findViewById(R.id.tv_time);
        viewHolder.m_tv_title = (TextView) view.findViewById(R.id.tv_title);
        viewHolder.m_tv_author = (TextView) view.findViewById(R.id.tv_author);
        viewHolder.m_tv_price = (TextView) view.findViewById(R.id.tv_price);
        viewHolder.m_tv_key = (TextView) view.findViewById(R.id.tv_key);

            // 把viewHolider裝進標籤
           view.setTag(viewHolder);

        } else {
           //如果 !=null,我就標籤的Hoviewlider 取出
           viewHolder = (ViewHolder) view.getTag();
        }

       //setter 封裝 Service_my_book
       if(service_my_book==null) {
            service_my_book = new Service_My_Book(myBooks);
       }

        //取得model 並索取資料
    Service_My_Book service = MyApp.getService_my_book();

    viewHolder.m_tv_time.setText(service.getTime(postion));
    viewHolder.m_tv_title.setText(service.getTitle(postion));
    viewHolder.m_tv_author.setText(service.getAuthor(postion));
    viewHolder.m_tv_price.setText(service.getPrice(postion));
    viewHolder.m_tv_key.setText(service.getKey(postion));
        return view;
   }

        //Holder (避免每下載一次就findviewbyId,有效利用ListView緩存機制)
   public class ViewHolder {
       private TextView m_tv_time;
       private TextView m_tv_title;
       private TextView m_tv_author;
       private TextView m_tv_price;
       private TextView m_tv_key;
   }
}


Step5 MainActivity:(main_activity)

JAVA :MainActivity
package com.example.app_cloud;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ListView;
import com.example.app_cloud.po.MyBook;
import com.google.gson.Gson;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;

 public class MainActivity extends AppCompatActivity {
   private Context context;
   private ListView listView;
   private MyBook[] collection_mybook;
   private ArrayAdapter arrayadapter;
   private BaseAdapter baseAdapter;

    //非常爛的方法 測試用
   private Handler handler=new Handler();
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       finID();

        Runnable r = new Runnable() {
           @Override
           public void run() {
               new MyThread().start();
               handler.postDelayed(this,2000);
           }
       };

  //不太好的方法,暫時測試用,下一章節再補充
       handler =new Handler();
       handler.post(r);
   }

    public void finID() {
        context = this;
       listView = (ListView) findViewById(R.id.listView);
   }

    public class MyThread extends Thread {
       String myjson;
       // step1.這段程式碼由OKhttp所提供
       // 分析 http 資料結構標準語法
       // Url是再把網址放入 這時候會發送 request方法給伺服器
       // 伺服器根據你要資料把資料用 response 回傳給你
      

        OkHttpClient client = new OkHttpClient();

        String run(String url) throws IOException {
           Request request = new Request.Builder()
                   .url(url)
                   .build();

            Response response = client.newCall(request).execute();
           return response.body().string();
       }

        //step3:創造出step2所要的工作內容
       Runnable r = new Runnable() {
           @Override
           public void run() {
               Gson gson = new Gson();

              collection_mybook=gson.fromJson(myjson,MyBook[].class);

                    //第1種 Adapter
               arrayadapter = new ArrayAdapter(
context
, android.R.layout.simple_expandable_list_item_1
, collection_mybook);

                    //第2種 Adapter
               baseAdapter =new JsonAdapter(context,collection_mybook);

                    //想要用哪一個就自己set
               listView.setAdapter(baseAdapter);
               arrayadapter.notifyDataSetChanged();
           }
       };

   //step2.
       // 運用OkHttp run的方法中,放入我們要讀取JSON的網址
       // 放入後再另一個排程, runOnUiThread(r),
       // runOnUiThread 就好像為了要持續改變 環境UI的方法
       // r就是工作的內容,這時候就要在new Runnable()出來。(上面)


       @Override
       public void run() {
           try {
               myjson = run("http://cloud1-1356.appspot.com/query");
               runOnUiThread(r);
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}