piątek, 1 stycznia 2016

Android - binding

Nowy rok zaczniemy małą dygresją dotyczącą data bindingu w Android.  Można powiedzieć, że powstało coś próbujące wzorować się na kompilowanym data bindingu w XAML. W porównaniu do tego, co było (zupełny brak bindingu) znaczący postęp, jednak w porównaniu do bindingu znanego z XAML (czy choćby Angulara 1 czy 2) wciąż bardziej złożony w użyciu… W każdym razie cieszy wchodzenie podejścia MVVM czy wzorca obserwatora w miejsce mediatora.

 

RoboBinding – framework MVVM dla Android

http://robobinding.github.io/RoboBinding/about.html

Butter Knife - biblioteka view “injection” dla Android

http://jakewharton.github.io/butterknife/

Android Annotations

http://androidannotations.org

Data Binding (Google) – generacja kodu (analiza pliku z layoutem, stworzenie klasy bindingu, stworzenie metod bindingu w pliku klasy),  gradle plugin

wsparcie w Android Studio:

1.3 +  (141.2117773) [Preferences –> Appearance & Behavior –> System Settings –> Updates]

 

prosty data binding

Android Plugin for Gradle 1.3.0 +

Data Binding Plugin 1.0-rc1 +

https://developer.android.com/tools/data-binding/guide.html#build_environment

Android SDK

Android Support Repository

Android Support Library

[Preferences –> Apperance & Behavior –> System Settings –> Android SDK]

projekt:

build.gradle

dependencies

classpath ‘com.android.tools.build:gradle:1.3.0’

classpath ‘com.android.databinding.dataBinder:1.0-rc1’

app/build.gradle

dodajemy apply plugin: ‘com.android.databinding’

w dependencies dodajemy  apt ‘com.android.databinding:compiler:1.0-rc1’

 

<layout>

<data>

         <variable

                  name=”dataSource”

                  type=”com.xxx.yyy.DataSource”/>        

</data>

<LinearLayout …>

       <TextView

                   …

                  android:text=”@{dataSource.message}”/>   // public String getMessage();  public String message(); 

                  // public String message;   inaczej błąd kompilacji

</LinearLayout>

</layout>

MainActivity (extends AppCompatActivity) onCreate

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);  //zamiast  setContentView

//ActivityMainBinding na podstawie activity_main.xml

binding.setDataSource(dataSource);

 

view

View view = inflater.inflate(R.layout.fragment_data, root, false);

FragmentDataBinding binding = DataBindingUtil.bind(view);  //lub FragmentDataBinding.bind(view);

FragmentDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_data, root, false);

FragmentDataBinding binding = FragmentDataBinding.inflate(inflater, root, false);

FragmentDataBinding binding = DataBindingUtil.getBinding(view);

 

bindowanie fragmentów

podobnie w layoucie

DataFragment extends Fragment, onCreateView:

FragmentDataBinding binding = FragmentDataBinding.inflate(inflater, container, false);

binding.setDataSource(dataSource);  //nazwa metody od DataSource

return binding.getRoot();

 

bindowanie załączonego layoutu

<include layout=”@layout/include_view

bind:dataSource=”@{dataSource}”/>

include_view.xml:

<layout>

<data>

          <variable

                    name=”dataSource”

                    type=”xxx.yyy.DataSource”/>

</data>

…  //binding TextView tak samo

</layout>

 

bindowanie załączonego własnego layoutu

w XML podobnie

 

View view = inflater.inflate(R.layout.data_view, root, false);

DataViewBinding binding = DataBindingUtil.bind(view);  //DataViewBinding.bind(view);

DataView extends FrameLayout

onFinishInflate:

binding = DataViewBinding.bind(this);

onAttachedToWindow:

binding.setDataSource(dataSource);

<include layout=”@layout/data_view”/>

 

bindowanie powtarzanych elementów

RecyclerView

projekt: dependencies:

+ compile ‘com.android.support.recyclerview-v7:22.2.1’

aktywność:

<android.support.v7.widget.RecyclerView android:id=”@+id/list”  …/>

item_view.xml – layout itemu, podobnie jak wcześniej

aktywność – klasa: onCreate

binding.list.setLayoutManager(new LinearLayoutManager(this));

binding.list.setAdapter(new DataSourceAdapter(getLayoutInflater()));

private class DataSourceAdapter extends RecyclerView.Adapter<ViewHolder> {

private LayoutInflater layoutInflater;

public DataSourceAdapter(LayoutInflater layoutInflater) {         

           this.layoutInflater = layoutInflater;

}

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

         ItemViewBinding binding = ItemViewBinding.inflate(layoutInflater, parent, false);

         return new ViewHolder(binding.getRoot());

}

@Override

public void onBindViewHolder(ViewHolder holder, int position) {

         …

         ItemViewBinding binding = DataBindingUtil.getBinding(holder.itemView);

         binding.setDataSource(dataSource);

}

@Override

public int getItemCount() {

        return 0;

}

}

private class ViewHolder extends RecyclerView.ViewHolder {

public ViewHolder(View itemView) {

          super(itemView);

}

}

Możliwość własnej klasy (widoku)

Wiele layoutów

 

stategie bindingu:

Inflate and Bind

Late Bind

Binding Reference

 

<data>

<variable name=”name” type=”String” />  //do bindowania atrybutów w layoucie

 

pobieranie / ustawianie wartości

layoutBinding.getName();

layoutBinding.setName(String name);

domyślne wartości są ustawiane w runtime  np. null dla name

 

<import type=”com.xxx.StringUtils” />

<TextView android:text=@{StringUtils.upper(name)}/>

 

<import type=”com.xxx.DataSource”/>

<import alias=”SqlDataSource” type=”javax.sql.DataSource”/>

<variable name=”data” type=”DataSource”/>

<variable name=”sql” type=”SqlDataSource”/>

 

<data class=”” />  //domyślny namespace  {module}.databinding, możliwość zmiany nazwy generowanej klasy bindingu i docelowego pakietu

<data class=”com.ddd.fff.eee.MainActivityBinding”/>

 

Layout Expression Language

odczyt i ustawianie propercji

bez new i super

wyliczanie i konwersja wartości

formatowany string

obsługa kolekcji

pobieranie elementu kolekcji na podstawie klucza lub indeksu

warunkowa logika

 

w strings.xml w <resources> m.in formatowane stringi z % i kolekcja string-array

android:visibility=”@{item.isSpecial ? View.VISIBLE : View:INVISIBLE}”

android:text=”@{@stringArray/sizes[item.index]}”

android:text=”@{item.map[‘xxx’]}”/>

android:text=”@{@string/price(item.map[‘price’])}”

 

czasami referencje do zasobów w wyrażeniach bindingu różnią się od typowego użycia

http://developer.android.com/tools/data-binding/guide.html

 

Własny i dynamiczny binding

Custom Binding Adapter

Default Binding Adapter – np. nie cachuje obrazków

Custom Event Listeners

Custom Event Binding

bindowanie typu listenera w layoucie

android:onClick=”@{listeners}”

lub

binding.setListeners(listeners)

 

Dynamiczny binding

bindowany typ jest ustawiany w runtime

T binding = DataBindingUtil.inflate(…)

 

custom binding adapter

znajdowanie metody settera w kontrolce na podstawie nazwy atrybutu w bindingu (np. text –> setText, nie zawsze jest tak prosto, źródła Android 6.0 SDK, API 23)

własny adapter

@BindingAdapter(“android:src”)

public static void setImageUri(ImageView view, String imageUri) {

if (imageUri == null)

        view.setImageURI((Uri)null);

else

        view.setImageURI(Uri.parse(imageUri));

}

@BindingConversion

public static ColorDrawable convertColorToDrawable(int color) {

return new ColorDrawable(color);

}

bindowanie numeru do propercji string, metoda z atrybutem BindingAdapter w aktywności

Event binding

auto

android:onClickListener=”@{listeners}”

public void setOnClickListener(View.OnClickListener l)

public interface OnClickListener {

void onClick(View v);

}

renamed

android:onClick=”@{listeners}”

@BindingMethods({

@BindingMethod(

    type = View.class,

    attributte =”android:onClick”

    method=”setOnClickListener”),…})

public class ViewBindingAdapter { … }

custom

OnAttachStateChangeListener

android:onViewAttachedToWindow=”@{listeners}”

android:onViewDetachedFromWindow=”@{listeners}”

 

handlery w event bindingu

 

dynamiczny binding

type=”List&lt;Person>”  //List<Person>

typ elementu w adapterze dla item-u w RecycleView

@Override

public int getItemViewType(int position) { … }

@Override

public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { …}

onBindViewHolder:  binding.setVariable(BR.person, person);  //(id, object)

 

 

Observables

activity

onCreate:

binding.setListeners(this);

public void onCheckChanged(CompoundButton view, boolean isChecked) {

binding.setIsOn(isChecked);

}

<data>

<variable name=”isOn” type=”boolean” />

<variable name=”listeners” type=”com.xxx.yyy.MainActivity”/>

<Switch … 

      android:onCheckedChanged=”@{listeners.onCheckChanged}”/>

 

możliwość MVVM

po każdej zmianie trzeba na nowo ustawić w bindingu viewModel, obsługa handlerów poza view modelem

można lepiej

public static class ViewModel extends BaseObservable {

        @Bindable public final ObservableBoolean isOn = new ObservableBoolean();

         …

}

nie trzeba wtedy na nowo ustawiać view modelu po każdej jego zmianie

viewModel.isOn.set(isChecked);

//od 1.0-rc2 +

Typy obserwowalne:

android.database

ContentObservable

android.view

ViewTreeObserver

rx.Observable

 

interfejs Observable

public interface Observable {

void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);

void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);

abstract class OnPropertyChangedCallback {

      public abstract void onPropertyChanged(Observable sender, int propertyId);

}

}

 

klasa BaseObservable

public class BaseObservable implements Observable {

public synchronized void notifyChange() { … }

public void notifyPropertyChanged(int fieldId) { … }

}

 

prymitywne Observables

ObservableBoolean

ObservableByte

ObservableChar

ObservableDouble

ObservableFloat

ObservableInt

ObservableLong

ObservableShort

 

Observable<T>  //nie implementuje Parcelable

ObservableParcelable<T extends Parcelable> // extends ObservableField<T>

ObservableList<> // konkretna implementacja:  ObservableArrayList<>

callback ma kilka metod (onChanged,  onItemRangeChanged, onItemRangeInserted, onItemRangeMoved, onItemRangeRemoved)

ListChangeRegistry

ObservableMap<> // –//-: ObservableArrayMap<>

callback: jedna metoda onMapChanged

MapChangeRegistry

 

własne obiekty obserwowalne:  Observable, BaseObservable lub pola ObservableField

public class CustomObservable implements BaseObservable {

private boolean breaker;

@Bindable public boolean getBreaker() {return breaker; }

public void setBreaker(boolean breaker) {

        if (this.breaker != breaker) {

              this.breaker = breaker;

              notifyPropertyChanged(BR.breaker);

          }

}

}

 

adaptery bindingu w osobnej klasie lub w aktywności lub view modelu

Brak komentarzy: