DataBinding Intro

From Hackmd : https://hackmd.io/FvbiZ6c9TLCS11_xEUM37w?both

outline

  • Basic Setting
  • Case 1 : Re-use
  • Case 2 : Listener
  • Case 3 : String concat
  • Case 4 : hide widget

Basic Setting


Gradle

android { 
    ....dataBinding {
        enabled = true
    }  
}

xml : activity_info.xml

<layout> 
    <LinearLayout 

    </LinearLayout>  
</layout>

Bind

ActivityInfoBinding mBinding 
= DataBindingUtil.setContentView(this, R.layout.activity_info);

Usage

mBinding.XXXX

Binding Data


Object (Kotlin)

@SuppressLint("ParcelCreator")  
@Parcelize  
class User(  
    @SerializedName("name")  
    var mName: String?
): Parcelable

xml

<layout> 
    <data>
        <variable name="user" type="com.example.User"/>  
    </data>

    <LinearLayout 
        
    </LinearLayout>  
</layout>

bind

mBinding.setUser(getUser());

Usage

<TextView   
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{user.MName}"/>


Case 1 : Re-use


idea


Just do it

  • LinearLayout(horizontal)
    • LinearLayout(vertical)
      • ImageView
        • android:src=”@drawable/……”
      • TextView
        • android:text=”@string/….”
    • LinearLayout(vertical)
      • ImageView
        • android:src=”@drawable/……”
      • TextView
        • android:text=”@string/….”
    • LinearLayout(vertical)
      • ImageView
        • android:src=”@drawable/……”
      • TextView
        • android:text=”@string/….”
    • LinearLayout(vertical)
      • ImageView
        • android:src=”@drawable/……”
      • TextView
        • android:text=”@string/….”

重複性太高


include xml (或是使用merge)

  • LinearLayout(vertical)
    • ImageView
    • TextView

original xml

  • LinearLayout(horizontal)
    • include
      • android:id=”@+id/….”
    • include
      • android:id=”@+id/….”
    • include
      • android:id=”@+id/….”
    • include
      • android:id=”@+id/….”

activity have to handle the change


mBinding.layoutAb.image.setImageDrawable(R.drawable.xxx);  
mBinding.layoutAb.textView.setText(getString(R.string.xxx));
mBinding.layoutCd.image.setImageDrawable(R.drawable.xxx);  
mBinding.layoutCd.textView.setText(getString(R.string.xxx));
mBinding.layoutEf.image.setImageDrawable(R.drawable.xxx);  
mBinding.layoutEf.textView.setText(getString(R.string.xxx));
mBinding.layoutGh.image.setImageDrawable(R.drawable.xxx);  
mBinding.layoutGh.textView.setText(getString(R.string.xxx));


another way

inclucde xml

<data>  
    <variable name="text" type="String"/>  
    <variable name="icon" type="android.graphics.drawable.Drawable"/>
</data>

TextView :

android:text="@{text}"

ImageView :

android:src="@{icon}"

orginal xml

app:icon="@{@drawable/myDrawable}"
app:text="@{@string/myText}"

result

  • LinearLayout(horizontal)
    • include
      • android:id=”@+id/….”
      • app:icon=”@{@drawable/myDrawable}”
      • app:text=”@{@string/myText}”

Timing


Case 2 : Listener


xml

  • LinearLayout(horizontal)
    • include
      • android:id=”@+id/layoutInclude1
    • include
      • android:id=”@+id/layoutInclude2
    • include
      • android:id=”@+id/layoutInclude3
    • include
      • android:id=”@+id/layoutInclude4

setOnClickListener

X

mBinding.layoutInclude1.setOnClickListener

O

findViewById(R.id.layoutInclude1).setOnClickListener();

another way

include xml

1.

<variable name="handler" type="XXXXXX.MainActivity"/>

2.

<variable name="handler" type="yourHandler"/>

android:onClick=”@{handler::onFeaturesClick}”


result in Activity

mBinding.layoutInclude1.setHandler(this);
public void onFeaturesClick(View view){}

advantage

Collect all handler together


Case 3 : String concat


Example

地址:XXXXXX


First way

<TextView
    android:text="@string/address"/>
<TextView
    android:text="@{info.address}"/>

second way

<TextView
    android:id="@+id/textAddress"
    android:text=""/>
mBinding.textAddress.setText(
getString(R.string.address) + info.address);

third way

<TextView
    android:id="@+id/textAddress"
    android:text="@{@string/address + info.address}"/>

another Example

output: 2018/2/15 16:48

mBinding.textTime.setText(
info.date + " " + info.time);

you can write

<TextView
    android:text="@{info.date + ` ` + info.time}"/>

little conclusion

1.顯示邏輯到底要寫在UI中還是必須拉到acitvity中 2.都寫在UI中會不會造成後面閱讀的人的困擾


Case 4 : hide widget


solution

android:visibility="@{isVisible ? View.VISIBLE : View.GONE}"
app:isVisible="@{true}"

Annotation Sample

目標:建立類似Butterknife的Annotation

Create a Annotation class

@Retention(CLASS) @Target(FIELD)
public @interface Bind {
    @IdRes int value();
}

write a Singleton class to handle TODO

public class AnnotationBind {
    private static AnnotationBind annotationBind;
    public static AnnotationBind instance() {
        synchronized (AnnotationBind.class){
            if(annotationBind == null){
                annotationBind = new AnnotationBind();
            } return annotationBind;
        }
    }

    public void inject(Object o){
        Class aClass = o.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field:declaredFields) {
            if(field.isAnnotationPresent(Bind.class)) {
                Bind annotation = field.getAnnotation(Bind.class);
                try {
                    field.setAccessible(true);
                    field.set(o, ((Activity)o).findViewById(annotation.value()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

inject the class when activity oncreate

    @Bind(R.id.text)
    TextView before;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AnnotationBind.instance().inject(this);

        before.setText("dddd");
    }

圖片拖曳

設定觸控時監聽

imageView.setOnTouchListener(imgListener);

計算出 actionBar長度以供設定圖片位置

Hint : 可將 actionBarHeight 存在全域

public void getActionBarHeight() {
    TypedValue tv = new TypedValue();
    if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true))
        actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics());
}

監聽拖曳動作並重設位置

private View.OnTouchListener imgListener = new View.OnTouchListener() {
    private float lengthXFromLeftToTouchEvent, lengthYFromTopToTouchEvent;
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {  
            case MotionEvent.ACTION_DOWN:
                //計算點下去的位置到圖片左上角位置的距離
                lengthXFromLeftToTouchEvent = event.getRawX() - imageView.getX();
                lengthYFromTopToTouchEvent = event.getRawY() - imageView.getY() - actionBarHeight;
                break;
                
            case MotionEvent.ACTION_MOVE:
                mx = (int) (event.getRawX() - lengthXFromLeftToTouchEvent);
                my = (int) (event.getRawY() - lengthYFromTopToTouchEvent - actionBarHeight);
                v.layout(mx, my, mx + v.getWidth(), my + v.getHeight());
                break;
                
            case MotionEvent.ACTION_UP:
                imageView.layout(mx, my, mx + imageView.getWidth(), my + imageView.getHeight());
                break;
        }
        return true;
    }
};

離開app時儲存資訊

@Override
protected void onPause() {
    super.onPause();
    sharedpreferencesEditor.saveInt("lengthXFromLeftToTouchEvent", mx);
    sharedpreferencesEditor.saveInt("lengthYFromTopToTouchEvent", my);
}

進入app時重設位置

public void initView() {
    imageView = findViewById(R.id.imageView);
    mx = sharedpreferencesEditor.readInt("lengthXFromLeftToTouchEvent");
    my = sharedpreferencesEditor.readInt("lengthYFromTopToTouchEvent");
    imageView.layout(mx, my, mx + imageView.getWidth(), my + imageView.getHeight());
}

補充

這裡提供兩種設定圖片位置的方法,但是不能混用

v.layout(mx, my, mx + v.getWidth(), my + v.getHeight());
v.setX(mx);
v.setY(mx);

Retrofit sample

From Hackmd : https://hackmd.io/jaW43Q3OTkCqnYJEU-NaWg?both

Outline

  • What is Retrofit?
  • Before start
  • Http libraries
  • Before start
  • Http libraries
  • OKhttp
  • Retrofit
  • Q&A

What is Retrofit?

Retrofit turns your HTTP API into a Java interface.


Before start

  • RESTful API
  • JSON
  • Gson
  • Annotation

RESTful API

rule or concept

  • 每一個URI代表一種資源
  • 客戶端通過四個HTTP動詞,對服務器端資源進行操作
    @GET("users/list")
    @GET("user/{userid}")
    

JSON & Gson

public static <T> T fromJson(String json, Class<T> klass) {
    return new Gson().fromJson(json, klass);
}
public static <T> List<T> fromJsonArray(String json, Class<T[]> klass) {
    T[] objects = new Gson().fromJson(json, klass);
    return objects == null ? new ArrayList<>() 
        : new ArrayList<T>(Arrays.asList(objects));
}
public static String toJson(Object object) {
    return new GsonBuilder()
            .create()
            .toJson(object);
}
public String toJson() {
    return new Gson().toJson(this);
}

Annotation

Usage

  1. Dagger
  2. Retrofit
  3. Gson
  4. DBflow
  5. Butterknife

How to build Annotation

@Bind(R.id.text)  
TextView text;
  1. build a Annotation class
  2. write a Singleton class to handle TODO
  3. inject the class when activity oncreate

preference : https://b013040034.github.io/ohmyblog/android/2018/02/06/annotation.html


Http libraries

  • HttpClient
  • OkHttp
  • Volley
  • retrofit

OKHttp


Find the bug 1

protected void onCreate(@Nullable Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    try {  
        OkHttpClient client = new OkHttpClient();  
        String url = MessageFormat.format(ApiClient.Postal, 408);  
        final Request request = new Request.Builder()  
                .url(ApiClient.BASE_URL + url)  
                .build();  
        client.newCall(request).execute().body().string();  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}

Error Msg 1

android.os.NetworkOnMainThreadException


Resolve 1

  1. build a Singleton
  2. init okhttp in constuctor
  3. write a method to handle request
public void getData(String url, Callback callback) {  
    final Request request = new Request.Builder()  
            .url(BASE_URL + url)  
            .build();  
   client.newCall(request).enqueue(callback);  
}


Callback -> Find the bug 2

apiClient.getDataWithCallBack(url, new Callback() {  
    @Override  
    public void onFailure(Call call, final IOException e) { 
        text.setText(e.getMessage());  
   }  
  
    @Override  
    public void onResponse(Call call, Response response) throws IOException {  
        final String resStr = response.body().string();  
        text.setText(resStr);  
   }  
});  

Error Msg 2

Only the original thread that created a view hierarchy can touch its views.


AsyncTask

new AsyncTask<String, Void, String>() {  
    @Override  
    protected String doInBackground(String... urls) {  
        try {  
            return apiClient.getDataWithAsync(url);  
        } catch (IOException e) {  
            return e.getMessage();  
        }  
    }  
    @Override  
    protected void onPostExecute(String resStr) {  
        text.setText(resStr);  
    }  
};

Okhttp Conclusion

  1. you have to make request in work thread or use callback
  2. you have to change to ui thread after callback –> thread safe –> Encapsulation 封裝

Encapsulation

Volley vs Retrofit

  1. Gson Get : call -> get object Post : make object -> call
  2. Restful Api easy way to set url and data

What I expect

Lazy Lazy Lazy Lazy


  1. No need to custom request -> No need to encapsulation
  2. Api class looks like Api Document -> easy to find & add & fix api
  3. get object and post object

Retrofit


dependencies

dependencies {
   compile 'com.squareup.retrofit2:retrofit:2.3.0'
   compile 'com.squareup.retrofit2:converter-gson:2.0.2'
}

Website

https://github.com/square/retrofit.


Singleton

public class ApiClient {
    public static final String BASE_URL = "https://api/";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit==null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

Api : Get

public interface ApiService {
    @GET("weather/{lat}/{lng}")
    Call<WeatherData> getWeather(@Path("lat") String lat, @Path("lng") String lng);
}

Api : Get (Volley and okHttp)

public static final String coordinate = "weather/{0}/{1}";
String url = MessageFormat.format(ApiClient.coordinate, lat, lng);

Convert json to Object

  1. Website http://www.jsonschema2pojo.org/
  2. Plugins


Object

public class WeatherData implements Serializable {
    @SerializedName("location")
    public String location;
    @SerializedName("temp")
    public float temperature;
}

You can use Kotlin + paracle instead


Get data

public void getWeatherData() {
        Retrofit retrofit = ApiClient.getClient();
        apiService = retrofit.create(ApiService.class);
        Call<WeatherData> data = apiService.getWeather("24","120");
        data.enqueue(new Callback<WeatherData>() {
            @Override
            public void onResponse(Call<WeatherData> call, Response<WeatherData> response) {
                WeatherData weatherData = response.body();
            }
            @Override
            public void onFailure(Call<WeatherData> call, Throwable t) {
             
            }
        });
    }

Api : Post

public interface ApiService {
    @POST("AccessToken")  
    Call<AccessToken> getTokenDebug(@Body AccessTokenInput tokenInput);

}

Post data

Call<AccessToken> dataDebug = apiService.getTokenDebug(accessToken);
dataDebug.enqueue(new Callback<AccessToken>() {  
    @Override  
    public void onResponse(Call<AccessToken> call, Response<AccessToken> response) {  
        AccessToken accessToken = response.body();  
        caculateResult(accessToken, textResultDebug);   
    }  
    @Override  
    public void onFailure(Call<AccessToken> call, Throwable t) {  
        textResultDebug.setText(t.getMessage());  
    }  
});


Api : Post (Volley)

  • StringRequest : with param
  • JsonObjectRequest : with json

Query String

@GET("news")
Call<News> getItem(@Query("type") String type);

@GET("news")
Call<News> getItem(@QueryMap Map<String, String> map);

Post with field

@FormUrlEncoded
@POST("news")
Call<User> postItem(@Field("type") String type);

@FormUrlEncoded
@POST("news")
Call<User> postItem(@FieldMap Map<String, String> map);

Other features

  • 簽名 : Signature
  • 標頭 : Headers
  • 檔案 : Multipart

signature

https://github.com/square/okhttp/wiki/Interceptors

Multipart

https://chenkaihua.com/2016/04/02/retrofit2-upload-multipart-files/


perference

http://square.github.io/retrofit/ https://ihower.tw/blog/archives/1542 https://itw01.com/V33WE9D.html https://bng86.gitbooks.io/android-third-party-/content/retrofit.html


glide usage

Gradle

dependencies {
  compile 'com.github.bumptech.glide:glide:4.0.0-RC1'
  annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC1'
}


Website

https://github.com/bumptech/glide.

Code

public static void setDrawableIntoImageView(Context context, int drawable, ImageView imageView) {
    Glide.with(context)
            .load(drawable)
            .into(imageView);
}
public static void setUriIntoImageView(Context context, Uri uri, ImageView imageView) {
    Glide.with(context)
            .load(uri)
            .into(imageView);
}
public static void setUrlIntoImageView(Context context, String url, ImageView imageView) {
    Glide.with(context)
            .load(url)
            .into(imageView);
}