LiveData 基本紀錄(Android Architecture Component)

LiveData 為 Android Architecture Component 其中之一,主要用途為持有可觀察數據(observable data),並讓有註冊且為活躍狀態的觀察者自動收到更新。

若了解或實作過Observer Pattern(觀察者模式)會很容易了解,可以把LiveData看作Subject(主題)也就是資料的來源。對Subject有興趣的observers可以訂閱主題,訂閱完成後當主題內的數據發生變化時,observers便會自動收到通知。

基本上 Observer Pattern 是用來取代 polling 的解決方案。關於Observer Pattern 可以參考這篇

回過頭來,首先看看LiveData的OverView(這裡是官網介紹)

首先LiveData是一種擁有可觀察數據的類別(看字面不好懂,直接看實作吧) e.g.,
  private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();

因為LiveData為abstract class,通常在實作上會以MutableLiveData(LiveData's subclass)取代LiveData。角括號可以傳入任何型別,包含Collections,list等等。

LiveData最大的特色為它具有生命週期感知(Lifecycle-aware)能力,該能力可以讓LiveData得知其他元件(Activity, Fragment, Service)的生命週期。
因此可以避免很多必須手動操作的情況,如螢幕旋轉時需要儲存數據,UI和數據之間的同步,memoey leak等等。

如何使用LiveData


1.建立LiveData實體並持有特定類型的數據。這通常在ViewModel類別內實作

2.建立Observer物件並定義onChanged方法,該方法內容用來描述當LiveData持有的數據改變時所作的事。你通常會在UI Controlller內(Activity, fragment)建立Observer物件

3.使用observe方法連結Observer物件和LiveData物件。observer方法需要傳入LifecycleOwner物件,這讓Observer物件訂閱LiveData物件並讓LiveData可以通知改變。你通常會在UI Controller內(activity, fragment)連結Observer物件

Note:
你可以藉由observeForever(Observer)方法來註冊一個observer而不需要結合LifecycleOwner。

在這種情況下observer會被當作永遠都是活躍的,因此總是會收到改變的資訊。反之,可以使用removeObserver(Observer)來移除observer

當LiveData的資料被更新之後,所有的已註冊且相關連的LifecycleOwner為活躍的observers會收到通知。

Example

1.Create LiveData objects


LiveData可以包含任何類型的數據,如List等等。
LiveData通常儲存在ViewModel內並透過getter方法來存取。
public class NameViewModel extends ViewModel {  

private MutableLiveData<String> mCurrentName = new MutableLiveData<String>();

public MutableLiveData<String> getCurrentName() {
return mCurrentName;
}
}

Note:確保LiveData儲存在ViewModel而不是activity或fragment

2.Observe LiveData objects


在大多數的情況下,onCreate方法為初始觀察LiveData位置。

在一般的情況下LiveData只在數據有更新時且觀察者為活躍的狀態才發送通知。但有2個例外,第1為當觀察者從非活躍狀態轉為活躍狀態時會收到通知。
第2為當觀察者是第2次從非活躍狀態轉為活躍狀態,則只有在自上次活動狀態以來該值發生變動時才會收到更新。
(也就是說當觀察者第1次從非活躍轉成活躍一定會收到更新。若是第2次從非活躍轉成活躍,且必須儲存的數據發生變化才會收到更新)
public class LiveDataMainActivity extends AppCompatActivity {  

private NameViewModel mViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.live_data_main_activity);

mViewModel = ViewModelProviders.of(this).get(NameViewModel.class);

Observer<String> nameObserver = new Observer<String>() {

@Override
public void onChanged(String newName) {
mName.setText(newName);
}
};

mViewModel.getCurrentName().observe(this, nameObserver);
}
}

 

3.Update LiveData objects


LiveData可以透過setValue和postValue方法來改變數據,2者的差別為setValue可用於main Thread,postValue可用於worker thread
String newName = "john doe";  
mViewModel.getCurrentName().setValue(newName);

因此當setValue呼叫之後,所有已註冊且為活躍的observer便會觸發onChanged方法

 

Orignal From: LiveData 基本紀錄(Android Architecture Component)

Gson 搭配 Gsonformat 基本使用紀錄

Gson 為 Google製作,可將JSON內容和java object互相轉換的library。

對我來說最大的用途就是可直接將Server回傳的json訊息直接轉換成java object,而不用再一一寫對應的 JsonObject。

而GsonFormat為 android studio 的外掛,用途是當你取得json內容之後,可以幫你產生對應的java object。

兩者搭配起來就可以方便的處理Server回傳訊息。

首先是Gson的部分:
這裡為Gson github連結,相關的安裝和使用都有介紹。

1.Gson Dependency
在 module 的 build.gradle 加入
dependencies {  
...
implementation 'com.google.code.gson:gson:2.8.5'
}

2.Using in code
2-1. Json to java object
首先假設有個User 類別為對應的 java object
public class User {  

private String name;
private int age;
private String sex;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}
}

2-2. java object to json
(就是將已存在的java物件轉成json 格式的string)
  @Test  
public void test_UserToJSON() {
User foxx = new User();
foxx.setAge(99);
foxx.setName("Foxx");
foxx.setSex("male");

Gson gson = new Gson();
String result = gson.toJson(foxx, User.class);
System.out.println("userToJSON:" + result);
assertEquals("{\"name\":\"Foxx\",\"age\":99,\"sex\":\"male\"}", result);
}

第9行即透過 gsong.toJson將 java object轉成 json string

2-3. json to java object
  @Test  
public void test_JSONToUser() {
Gson gson = new Gson();
String source = "{\"name\":\"Foxx\",\"age\":99,\"sex\":\"male\"}";
User target = gson.fromJson(source, User.class);
assertNotNull(target);
}

第5行透過gson.fromJson將json string 轉成 java object

 

接著是GsonFormat
1.Install GsonFormat
安裝方式和一般的android studio 外掛相同。
開啟 android studio -> File -> Settings -> Plugins -> Browse repositories
->輸入 gsonformat -> install -> 安裝完成後 重開 android studio

2.How to use
2-1.首先手動建立對應的空class(只有類別名稱無其他內容),以上面的User為例,我們就先建立一個User的空class。
public class User {  

}

2-2.產生對應json 的內容
假設已取得Server回傳的json string 如下
{  
"name":"Foxx",
"sex":"male",
"age":"99"
}

回到2-1建立的User class,點擊上方工具欄的Code,再點擊 Generate,選擇GsonFormat,在顯示的視窗中貼上2-2的內容。


點擊OK -> 再點擊OK,完成後User class 如下
public class User {  

private String name;
private String sex;
private String age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String getAge() {
return age;
}

public void setAge(String age) {
this.age = age;
}
}

從第3~29行即是GsonFormat產生的內容。

Orignal From: Gson 搭配 Gsonformat 基本使用紀錄

OKHttp 基本使用紀錄

OKHttp 是支援HTTP & HTTP/2 的開源框架,透過它可以很方便發送HTTP請求。
具有以下特性:
1.API設計簡單,可以透過幾行code即可發送HTTP請求
2.支援同步,異步請求。同步請求會block目前的Thread,異步請求則不會block

OKHttp官網連結

最新版本釋出

以下紀錄最基本的get, post, post with json format 的使用方式。


1.相依性


在 module 的 build.gradle 加入
dependencies {  
...
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
}

Note:目前(2018/09/28)最新版本為3.11.0

2. Using in code


為了封裝OKHttp建立了OKHttpWrapper 類別,外部皆透過該類別來使用OKHttp。
OKHttpWrapper.java
import android.util.Log;  
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONObject;

public class OKHttpWrapper {

private static final String TAG = OKHttpWrapper.class.getSimpleName();

private static OkHttpClient sOKHttpClient = new OkHttpClient();

public static void sendGet(String URL, Map<String, String> requestParameter, Callback callback) {
Set<String> keys = requestParameter.keySet();
for (String value : keys) {
URL = new StringBuilder(URL + "&" + value + "=" + requestParameter.get(value)).toString();
}
Request request = new Request.Builder().url(URL).build();
Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

public static void sendPost(String URL, Map<String, String> requestParameter, Callback callback) {
FormBody.Builder builder = new FormBody.Builder();
Set<String> keys = requestParameter.keySet();
for (String key : keys) {
builder.add(key, requestParameter.get(key));
}

Request request = new Request.Builder().url(URL).post(builder.build()).build();

Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

public static void sendPostWithJSONFormat(String URL, Map<String, String> requestParameter,
Callback callback) {
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody requestBody = RequestBody.create(JSON, new JSONObject(requestParameter).toString());
Request request = new Request.Builder().url(URL).post(requestBody).build();
Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

}

第19行建立OKHttpClient實體,各個請求皆透過該實體來動作。
第21行sendGet方法為發送GET使用,第1個參數為接收請求的URL,第2個為請求的參數,第3個參數為發送請求之後的回應。
第31行sendPost方法為發送POST使用,第1個參數為接收請求的URL,第2個為請求的參數,第3個參數為發送請求之後的回應。
第44行sendPostWithJSONFormat方法為發送POST但參數的格式為JSON,第1個參數為接收請求的URL,第2個為請求的參數,第3個參數為發送請求之後的回應。

可以看到透過OKHttp發送請求相當簡單,接下來示範外部如何呼叫OKHttpWrapper。接收請求的Server使用httpbin
以下為外部
  private void testSendRequest(String name, String id) {  
Map<String, String> httpParameter = new HashMap<>();
httpParameter.put("Name", name);
httpParameter.put("ID", id);

OKHttpWrapper.sendPostWithJSONFormat(
"http://httpbin.org/post", httpParameter, new Callback() {
@Override
public void onFailure(Call call, IOException e) {

}

@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, TAG+" response:"+response.body().string());
}
});
}

第2~4行為組裝請求的參數。
第6行呼叫OKHttpWrapper的sendPostWithJSONFormat方法
第1個參數為為接受請求的URL,第2個參數為請求的參數,第3個參數為發送請求後的回應,其中onFailure為請求失敗的回應。onResponse則為請求成功的回應。
        testSendRequest("Foxx", "999");  
testSendRequest("Peter", "000");
testSendRequest("May", "111");

Output
 "json": {  
"ID": "999",
"Name": "Foxx"
},

"json": {
"ID": "000",
"Name": "Peter"
},

"json": {
"ID": "111",
"Name": "May"
},

 

Orignal From: OKHttp 基本使用紀錄

Arrays.binarySearch 直接使用於 android resource 的問題

為了方便使用陣列常常會直接在array.xml中宣告2個互相對應的陣列 e.g.,
  <string-array name="type">  
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
<string-array name="code">
<item>A</item>
<item>C</item>
<item>B</item>
<item>E</item>
<item>F</item>
<item>G</item>
<item>D</item>
</string-array>

若直接在code中使用Arrays.binsearch 會出現 index 錯誤 e.g.,
        String[] targetArray = getResources().getStringArray(R.array.type);  
int index = Arrays.binarySearch(targetArray, 3);
String[] codeArray = getResources().getStringArray(R.array.code);

原因是因為Arrays.binarySearch只能使用在已經排序(自然排序)過的陣列。

若該陣列還未排序會丟出Exception,說明index有問題。

比較快的解決方式就是自己寫取得對應的元素 e.g.,
    private int findSpecifyIndexInArray(String[] targetArray, String specify){  
for (int i = 0; i < targetArray.length; ++i) {
if (targetArray[i].equals(specify)) {
return i;
}
}
return -1;
}

String[] targetArray = getResources().getStringArray(R.array.type);
int index = findSpecifyIndexInArray(targetArray, 3);
String[] codeArray = getResources().getStringArray(R.array.code);

 

Orignal From: Arrays.binarySearch 直接使用於 android resource 的問題

Google Cloud Source Repositories 如何將本地端已存在的repo推送到遠端

狀況:本地端已存在git repo,現在想將本地端的repo推送到遠端的新repo

1.先到GCSR新增遠端新repo
https://source.cloud.google.com/repos ->點擊新增存放區->點擊建立新的存放區->點擊繼續->輸入存放區名稱->選擇已存在專案或新增專案->點擊建立

2.建立本地端repo的認證並推送到遠端
在"請選取將程式碼推送至存放區的選項:"選擇"從本機 Git 存放區推送程式碼"
->在"選取您偏好的驗證方式:"選擇"手動產生的憑證" ->點擊 1.產生及儲存Git憑證->將Configure Git 頁面中的 Windows Users 程式碼複製並貼到本地端的git bash -> 移動到本地端專案路徑並進行 2. 進行將 Cloud Repository 新增為遠端存放區:(這裡注意最好編輯成1行再貼到git bash,如 git remote add google https://xxxx)->最後進行 3.從本機 Git 存放區推送程式碼:
(注意若出現
fatal: remote error:  


Invalid authentication credentials.

Please generate a new identifier:
https://source.developers.google.com/new-password

代表要重新產生憑證,到https://source.developers.google.com/auth/start?scopes=https://www.googleapis.com/auth/cloud-platform
選擇登入帳號,將顯示的程式碼貼到git bash)

 

3.完成後在GCSR的repo便可看到推送的內容。

Orignal From: Google Cloud Source Repositories 如何將本地端已存在的repo推送到遠端

Google Cloud Source Repositories 試用紀錄

GCSR(Google Cloud Source Repositories) 是 google 推出以 git 為基礎的程式碼代管服務。

原先我的side project是自己建git server來放置source code,後來因為硬體故障等等

自己維護需要花費不少時間和成本,加上本身也是google 的愛好者,最後選擇GCSR來試試。

首先談談GCSR的缺點,基本上最大麻煩的點為google 提供的repository必須依附於google cloud platform(GCP)上的專案。

也就是說你必須先了解基本的GCP使用方式才能開始用GCSR。

第2個麻煩的點在於create, clone repository在官方教學中必須透過 google cloud sdk才能使用(也許可以透過別的方式但還未試出),也就是說你必須先安裝 google cloud sdk才能開始使用GCSR。

第3個麻煩的點在於免費版的限制,如下







免費方案
超量


最多 5 位使用者
每位專案使用者每月 $1 美元 (專案使用者達 5 位以上時)

50 GB 儲存空間
每 GB 儲存空間 每月 $0.10 美元

50 GB 輸出
每 GB 輸出每月 $0.10 美元



若以上3個限制您都還可以接受,那就繼續往下看吧。

首先提供官網GCSR的說明。若想直接參考使用方式請看quick start。

1.在Google Cloud Platform建立專案
建議新增獨立的GCP專案來放置repository避免和其他的專案混淆。

2.安裝Google Cloud SDK
安裝SDK的目的就是用來新增,複製專案(應該是可以透過其他方式來做到但還未試出)。
注意安裝完成之後還需要進行初始化的動作,參考這裡

3.安裝git
Windows 官方推薦使用git-scm

Linux就用command line安裝吧

4.使用Google Cloud SDK(gcloud command line) 建立 repository
gcloud source repos create hello-world

clone repo 到本地端
gcloud source repos clone hello-world

從官網看起來似乎只有 create和clone需要使用 gcloud

其它的操作就可以使用一般的git指令,如 git add, git commit, git push等等

5. GCSR UI
當然就像github,gitlab等等,GCSR也有提供repository的UI操作。
以下為repository的介面,存放區即為repository


以下是搶先試用版的畫面


Orignal From: Google Cloud Source Repositories 試用紀錄

使用 RingtoneManager 播放提示音(Notification),鬧鐘(Alarm),來電鈴聲(Ringtone)

1.播放預設的提示音。
  public static void playDefaultNotification(Context context) {
    Uri defaultNotificationURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    Ringtone soundOfDefaultNotification = RingtoneManager.getRingtone(context, defaultNotificationURI);
    soundOfDefaultNotification.play();
  }
其中RingtoneManager.TYPE_NOTIFICATION為提示音的設定。

若想播放預設的鬧鐘只要改為RingtoneManager.TYPE_ALARM
播放預設的鬧鐘只要改為RingtoneManager.TYPE_RINGTONE

2.播放裝置內其他的提示音。
首先取得裝置內的提示音清單。

  public static Map<String, String> getSoundTitleAndUri(Context context, int type) {
    RingtoneManager manager = new RingtoneManager(context);
    manager.setType(type);
    Cursor cursor = manager.getCursor();

    Map<String, String> titleAndUri = new HashMap<>();
    while (cursor.moveToNext()) {
      String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
      String id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX);
      String uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
      titleAndUri.put(title, uri+"/"+id);
    }

    return titleAndUri;
  }
 
使用的方式如下(取得提示音的清單)

    getSoundTitleAndUri(context, RingtoneManager.TYPE_NOTIFICATION);
回傳的Map其key值即為提示音的名稱,value值為提示音的Uri。

若想取得的是鬧鐘清單,只要改為RingtoneManager.TYPE_ALARM即可
若想取得的是來電鈴聲清單,只要改為RingtoneManager.TYPE_RINGTONE即可

若把該Map印出來如下。
key:Alloy value:content://media/internal/audio/media/89
key:Arc value:content://media/internal/audio/media/90
key:Bezel value:content://media/internal/audio/media/91
...
Alloy為提示音的名稱,其URI為 content://media/internal/audio/media/89

最後要播放指定的提示音如下

  public static void playSound(Context context, String uri) {
    Uri soundUri = Uri.parse(uri);
    Ringtone sound = RingtoneManager.getRingtone(context, soundUri);
    sound.play();
  }
使用的方法為

  playSound(context,"content://media/internal/audio/media/89");//play Alloy

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Affiliate Network Reviews