Upload
nelson-glauber-leal
View
1.779
Download
1
Embed Size (px)
Citation preview
Dominando o Data Binding no Android
+Nelson Glauber@nglauber www.nglauber.com.br
@nglauber
+NelsonGlauber
www.nglauber.com.br
\
Por que Data Binding?
Data Binding
• Facilita a ligação entre Model e View
• Estende os arquivos de layout com micro-expressões
• Binders customizados reduzem a repetição de código
• Tratamento de NPE
Estudo de caso
public class Book {
private String id; private String title; private String author; private String coverUrl; private int pages; private int year; private Publisher publisher; private boolean available; private MediaType mediaType; private float rating;
// Getters and setters... }
Book book = (Book)getIntent().getSerializableExtra(EXTRA_LIVRO);
ImageView imgCover = (ImageView) findViewById(R.id.image_cover); TextView txtTitle = (TextView) findViewById(R.id.text_title); TextView txtAuthor = (TextView) findViewById(R.id.text_author); TextView txtPages = (TextView) findViewById(R.id.text_pages); TextView txtYear = (TextView) findViewById(R.id.text_year); TextView txtPublisher = (TextView) findViewById(R.id.text_publisher); TextView txtAvailable = (TextView) findViewById(R.id.text_available); TextView txtMediaType = (TextView) findViewById(R.id.text_media_type); RatingBar ratingBook = (RatingBar) findViewById(R.id.rating_book);
Glide.with(this).load(book.getCoverUrl()).into(imgCover); txtTitle.setText(book.getTitle()); txtAuthor.setText(book.getAuthor()); txtPages.setText(getString(R.string.text_format_book_pages, book.getPages())); txtYear.setText(getString(R.string.text_format_book_year, book.getYear())); txtPublisher.setText(book.getPublisher().getName()); txtAvailable.setText(book.isAvailable() ? R.string.text_book_available : R.string.text_book_unavailable); txtMediaType.setText(book.getMediaType().toString()); ratingBook.setNumStars(book.getRating());
book.setCoverUrl(imageFile.getAbsolutePath()); book.setTitle(editPages.getText().toString()); book.setAuthor(editAuthor.getText().toString()); book.setPages(Integer.parseInt(editPages.getText().toString())); book.setYear(Integer.parseInt(editYear.getText().toString())); book.setPublisher((Publisher) spinnerPublisher.getSelectedItem()); book.setAvailable(checkAvailable.isChecked()); book.setMediaTypeValue(radioMediaEbook.isChecked() ? MediaType.EBOOK : MediaType.PAPER); book.setRating(ratingBook.getRating()); saveBook(book);
Book book = (Book)getIntent().getSerializableExtra(EXTRA_BOOK);
ActivityDetailViewBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_detail_view);
mBinding.setBook(book);
Conceitos básicos
Configuração
Passo 1…android { ... dataBinding { enabled true } }
…e pronto!
res/layout/activity_main.xml
activity_main.xml
ActivityMainBinding
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout ...> <TextView ... android:id=“@+id/text_view_name” android:text="Hello World!" /> </LinearLayout> </layout>
public class MainActivity extends AppCompatActivity {
ActivityMainBinding mBinding;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); mBinding.textViewName.setText(“DataBinding is cool!!!”); } }
MainActivity.java
camelCase
Se não gostar do nome da classe…
MainActivity.java
res/layout/activity_main.xml
<layout ...> <data class="VaiFilhaoBinding"> ... </data>
public class MainActivity extends AppCompatActivity {
VaiFilhaoBinding binding; ...
Mapeando objetos na UI
Book book = (Book)getIntent().getSerializableExtra(EXTRA_LIVRO);
ImageView imgCover = (ImageView) findViewById(R.id.image_cover); TextView txtTitle = (TextView) findViewById(R.id.text_title); TextView txtAuthor = (TextView) findViewById(R.id.text_author); TextView txtPages = (TextView) findViewById(R.id.text_pages); TextView txtYear = (TextView) findViewById(R.id.text_year); TextView txtPublisher = (TextView) findViewById(R.id.text_publisher); TextView txtAvailable = (TextView) findViewById(R.id.text_available); TextView txtMediaType = (TextView) findViewById(R.id.text_media_type); RatingBar ratingBook = (RatingBar) findViewById(R.id.rating_book);
Glide.with(this).load(book.getCoverUrl()).into(imgCover); txtTitle.setText(book.getTitle()); txtAuthor.setText(book.getAuthor()); txtPages.setText(getString(R.string.text_format_book_pages, book.getPages())); txtYear.setText(getString(R.string.text_format_book_year, book.getYear())); txtPublisher.setText(book.getPublisher().getName()); txtAvailable.setText(book.isAvailable() ? R.string.text_book_available : R.string.text_book_unavailable); txtMediaType.setText(book.getMediaType().toString()); ratingBook.setNumStars(book.getRating());
Book book = (Book)getIntent().getSerializableExtra(EXTRA_BOOK);
ActivityDetailViewBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_detail_view);
mBinding.setBook(book);
<layout xmlns:android="http://schemas.android.com/apk/res/android" ...> <data> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <LinearLayout ... > <ImageView android:src="@{book.coverUrl}" ... /> <TextView android:text="@{book.title}" ... /> <TextView android:text="@{book.author}" ... /> <TextView android:text="@{@string/text_format_book_pages(book.pages)}" ... /> <TextView android:text="@{@string/text_format_book_year(book.year)}" ... /> <TextView android:text="@{book.publisher.name}" ... /> <TextView android:text="@{book.available ? @string/text_book_available : @string/text_book_unavailable}" ... /> <TextView android:text="@{book.mediaType}" ... /> <RatingBar android:rating="@{book.rating}" ... /> </LinearLayout> </layout>
<layout xmlns:android="http://schemas.android.com/apk/res/android" ...> <data> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <LinearLayout ... > <ImageView android:src="@{book.coverUrl}" ... /> <TextView android:text="@{book.title}" ... /> <TextView android:text="@{book.author}" ... /> <TextView android:text="@{@string/text_format_book_pages(book.pages)}" ... /> <TextView android:text="@{@string/text_format_book_year(book.year)}" ... /> <TextView android:text="@{book.publisher.name}" ... /> <TextView android:text="@{book.available ? @string/text_book_available : @string/text_book_unavailable}" ... /> <TextView android:text="@{book.mediaType}" ... /> <RatingBar android:progress="@{book.rating}" ... /> </LinearLayout> </layout>
<!-- strings.xml --> <string name="text_format_book_pages">Número de páginas: %1$d</string> <string name="text_format_book_year">Ano de publicação: %1$d</string>
<layout xmlns:android="http://schemas.android.com/apk/res/android" ...> <data> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <LinearLayout ... > <ImageView android:src="@{book.coverUrl}" ... /> <TextView android:text="@{book.title}" ... /> <TextView android:text="@{book.author}" ... /> <TextView android:text="@{@string/text_format_book_pages(book.pages)}" ... /> <TextView android:text="@{@string/text_format_book_year(book.year)}" ... /> <TextView android:text="@{book.publisher.name}" ... /> <TextView android:text="@{book.available ? @string/text_book_available : @string/text_book_unavailable}" ... /> <TextView android:text="@{book.mediaType}" ... /> <RatingBar android:progress="@{book.rating}" ... /> </LinearLayout> </layout>
Tá pronto? Pode rodar???
NÃO!
<layout xmlns:android="http://schemas.android.com/apk/res/android" ...> <data> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <LinearLayout ... > <ImageView android:src="@{book.coverUrl}" ... /> <TextView android:text="@{book.title}" ... /> <TextView android:text="@{book.author}" ... /> <TextView android:text="@{@string/text_format_book_pages(book.pages)}" ... /> <TextView android:text="@{@string/text_format_book_year(book.year)}" ... /> <TextView android:text="@{book.publisher.name}" ... /> <TextView android:text="@{book.available ? @string/text_book_available : @string/text_book_unavailable}" ... /> <TextView android:text="@{book.mediaType}" ... /> <RatingBar android:progress="@{book.rating}" ... /> </LinearLayout> </layout>
public class Book { ... private String coverUrl; private MediaType mediaType; }
Binding Adapters #1
public class TextBinding {
@BindingAdapter({"android:text"}) public static void setMediaTypeText(TextView textView, MediaType mediaType){ Context context = textView.getContext(); switch (mediaType) { case EBOOK: textView.setText(context.getString(R.string.text_book_media_ebook)); break; case PAPER: textView.setText(context.getString(R.string.text_book_media_paper)); break; default: textView.setText(null); } } }
TextBinding.java
public class ImageBinding {
@BindingAdapter({"android:src"}) public static void setImageUrl(ImageView imageView, String url){ Glide.with(imageView.getContext()) .load(url) .into(imageView); }
@BindingAdapter({"android:src", "placeHolder"}) public static void setImageUrl(ImageView imageView, String url, Drawable placeholder){ Glide.with(imageView.getContext()) .load(url) .placeholder(placeholder) .into(imageView); } }
ImageBinding.java
<ImageView android:id="@+id/image_cover" android:layout_width="200dp" android:layout_height="200dp" android:layout_gravity="center" android:background="#CCC" android:scaleType="centerCrop" android:src="@{book.coverUrl}" app:placeHolder=“@{@drawable/ic_photo}”/>
res/values/attrs.xml
<attr name="placeHolder" format="reference"/>
Fragments e Adapters
BookFragment.java
private FragmentBookBinding mBinding;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_book, container, false); ... return mBinding.getRoot(); }
res/layout/item_book.xml
<layout ...> <data> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book"/> </data>
<android.support.v7.widget.CardView ...> <RelativeLayout ...> <ImageView android:src="@{book.coverUrl}" ... /> <TextView android:text="@{book.title}" ... /> <TextView android:text="@{book.author}" ... /> </RelativeLayout> </android.support.v7.widget.CardView> </layout>
BookViewHolder.java
public static class BookViewHolder extends RecyclerView.ViewHolder {
ItemBookBinding binding;
public BookViewHolder(ItemBookBinding binding) { super(binding.getRoot()); this.binding = binding; } }
@Override public BookViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemBookBinding binding = DataBindingUtil.inflate( LayoutInflater.from(parent.getContext()), R.layout.item_book, parent, false);
final BookViewHolder vh = new BookViewHolder(binding); vh.itemView.setOnClickListener(...); return vh; }
@Override public void onBindViewHolder(BookViewHolder holder, int pos) { Book book = mBooksList.get(pos); holder.binding.setBook(book); holder.binding.executePendingBindings(); }
BookAdapter.java
E se os dados mudarem?
public class Book extends BaseObservable { // Atributos...
@Bindable public String getTitle() { return title; } public void setTitle(String title) { this.title = title; notifyPropertyChanged(BR.title); } @Bindable public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; notifyPropertyChanged(BR.author); }
// Demais getters/setters
Book.java
public class Book implements Observable {
// Copie as coisas do BaseObservable :) }
public class BaseObservable implements Observable { private transient PropertyChangeRegistry mCallbacks;
public BaseObservable() { } @Override public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) { if (mCallbacks == null) { mCallbacks = new PropertyChangeRegistry(); } mCallbacks.add(callback); } @Override public synchronized void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback) { if (mCallbacks != null) { mCallbacks.remove(callback); } } public synchronized void notifyChange() { if (mCallbacks != null) { mCallbacks.notifyCallbacks(this, 0, null); } } public void notifyPropertyChanged(int fieldId) { if (mCallbacks != null) { mCallbacks.notifyCallbacks(this, fieldId, null); } } }
import android.databinding.ObservableBoolean; import android.databinding.ObservableField; import android.databinding.ObservableInt;
public class Book { //... private final ObservableField<String> title; private final ObservableInt year; private final ObservableBoolean available;
public Book() { //... this.title = new ObservableField<>(); this.year = new ObservableInt(2016); this.available = new ObservableBoolean(false); }
public ObservableField<String> getTitle() { return title; }
public void setTitle(String title) { this.title.set(title); }
public ObservableInt getYear() { return year; }
public void setYear(double year) { this.year.set(year); }
public ObservableBoolean isAvailable() { return available; }
public void setAvailable(boolean available) { this.available.set(available); } }
Two-way Data Binding
<layout ...> <data> <variable name="texto" type="String"/> </data>
<LinearLayout ... > <TextView ... android:id="@+id/textViewNome" android:text="@{texto}" />
<EditText ... android:id="@+id/editTextNome" android:text="@={texto}"/> </LinearLayout> </layout>
<layout ...> <data> ... <import type="br.com.nglauber.livrosfirebase.model.MediaType" /> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <LinearLayout ...> <ImageView android:src="@{book.coverUrl}" ... /> <EditText android:text="@={book.title}" ... /> <EditText android:text="@={book.author}" ... /> <EditText android:text="@={book.pages}" ... /> <EditText android:text="@={book.year}" ... /> <Spinner ...> <!-- trataremos desse em breve. --> <CheckBox android:checked="@={book.available}" ... /> <RadioGroup ...> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.EBOOK}" ... /> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.PAPER}" ... /> </RadioGroup> <RatingBar android:rating="@={book.rating}" ... /> ... </LinearLayout> </layout>
<layout ...> <data> ... <import type="br.com.nglauber.livrosfirebase.model.MediaType" /> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> </data> <LinearLayout ...> <ImageView android:src="@{book.coverUrl}" ... /> <EditText android:text="@={book.title}" ... /> <EditText android:text="@={book.author}" ... /> <EditText android:text="@={book.pages}" ... /> <EditText android:text="@={book.year}" ... /> <Spinner ...> <!-- trataremos desse em breve. --> <CheckBox android:checked="@={book.available}" ... /> <RadioGroup ...> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.EBOOK}" ... /> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.PAPER}" ... /> </RadioGroup> <RatingBar android:progress="@={book.rating}" ... /> ... </LinearLayout> </layout>
Binding Adapters #2
Cuidado com loops!
EditText android:text="@={book.title}"
public class Book extends BaseObservable { // Atributos...
@Bindable public String getTitle() { return title; } public void setTitle(String title) { this.title = title; notifyPropertyChanged(BR.title); } // Demais getters/setters
public class EditTextBindingAdapters {
@InverseBindingAdapter(attribute = "android:text") public static int getTextAsInt(EditText editText) { try { return Integer.parseInt(editText.getText().toString()); } catch (Exception e){ return 0; } }
@BindingAdapter({"android:text"}) public static void setTextFromInt(EditText editText, int value){ if (getTextAsInt(editText) != value) { editText.setText(String.valueOf(value)); } } }
EditTextBindingAdapters.java
Tratamento de eventos
<layout ...> <data> ... <variable name="presenter" type=“br.com.nglauber.livrosfirebase.DetailEditActivity"/> </data>
<LinearLayout ...> ... <TextView ... android:onLongClick="@{presenter::longClick}" /> <Button ... android:onClick=“@{presenter::clickSaveBook}”/> <EditText ... android:onFocusChange="@{presenter::focusChanged}"/> </LinearLayout> </layout>
res/layout/activity_detail_edit.xml
@Override protected void onCreate(Bundle savedInstanceState) { ... binding.setBook(book); binding.setPresenter(this); }
public void clickSaveBook(View view) { Toast.makeText(this, book.toString(), Toast.LENGTH_SHORT).show(); }
public boolean longClick(View view){ book.setRating(book.getRating() + 1); return true; }
public void focusChanged(View v, boolean focus){ if (!focus) showProduct(v); }
DetailEditActivity.java
Respeite a assinatura do
método
<layout ...> <data> <import type="br.com.nglauber.livrosfirebase.model.MediaType" /> ... <variable name="presenter" type="br.com.nglauber.livrosfirebase.DetailEditActivity" /> </data> <LinearLayout ...> ... <RadioGroup ...> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.EBOOK}" android:onCheckedChanged="@{presenter::onMediaTypeChanged}" /> <RadioButton ... android:checked="@{book.mediaTypeValue == MediaType.PAPER}" android:onCheckedChanged="@{presenter::onMediaTypeChanged}" /> </RadioGroup> ... </LinearLayout> </layout>
public void onMediaTypeChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { if (buttonView == mBinding.radioMediaEbook) { mBinding.getBook().setMediaTypeValue(MediaType.EBOOK);
} else if (buttonView == mBinding.radioMediaPaper) { mBinding.getBook().setMediaTypeValue(MediaType.PAPER); } } }
public class Publisher {
private String id; private String name;
public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return name; } }
public class Book extends BaseObservable {
private Publisher publisher; ... @Bindable public Publisher getPublisher() { return publisher; }
public void setPublisher(Publisher publisher) { this.publisher = publisher; notifyPropertyChanged(BR.publisher); } }
<layout ...> <data> <import type="java.util.List"/>
<variable name="book" type="br.com.nglauber.livrosfirebase.model.Book" /> <variable name="presenter" type="br.com.nglauber.livrosfirebase.DetailEditActivity" /> <variable name="publishers" type="java.util.List<br.com.nglauber.livrosfirebase.model.Publisher>" /> </data>
<LinearLayout ...> <Spinner ... android:entries="@{publishers}" android:selection="@{publishers.indexOf(book.publisher)}" android:onItemSelected="@{(p, v, pos, id)->book.setPublisher(publishers[pos])}" /> </LinearLayout> </layout>
DetailEditActivity.java
ObservableList<Publisher> publishers = loadListFromSomewhere(); mBinding.setPublishers(publishers);
<layout ...> <data> <variable name="book" type="br.com.nglauber.livrosfirebase.model.Book"/> <variable name="presenter" type=“br.com.nglauber.livrosfirebase.DetailEditActivity"/> </data>
<LinearLayout ...> ... <Button ... android:onClick="@{()->presenter.saveBook(book)}"/> </LinearLayout> </layout>
Pronto!
http://jakewharton.github.io/butterknife/
Binding Adapters #3
https://github.com/lisawray/fontbinding
public class TextViewBinderAdapter { @BindingAdapter({"font"}) public static void setFont(TextView textView,String font){ AssetManager assets = textView.getContext().getAssets();
Typeface typeface = Typeface.createFromAsset(assets, font);
textView.setTypeface(typeface); } }
<TextView app:font="@{`FunnyKid.ttf`}" .../>
Mas se tiver um “set” pode usar!
<android.support.v4.widget.DrawerLayout ... app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}"/>
Miscellaneous
Expression Language
<TextView ... android:text="@{user.displayName ?? user.lastName}"/>
<TextView ... android:text="@{`User: ` + user.name}”/>
<LinearLayout ... android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"/>
res/values/colors.xml
<color name="red">#F00</color> <color name="black">#000</color>
<TextView ... android:id="@+id/text_title" android:textColor="@{book.available ? @color/black : @color/red}" android:text="@{book.title}" />
res/values/strings.xml
<string name="price_format">$ %1$.2f</string>
<TextView ... android:id="@+id/text_price" android:text="@{@string/price_format(book.price)}" />
getString(R.string.price_format, product.price);
Include
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="book" type=“...Book”/> <variable name=“presenter" type=“...DetailEditActivity" /> </data> <LinearLayout ...> <include android:id="@+id/content" layout="@layout/content_detail_edit" app:presenter="@{presenter}" app:book="@{book}" /> </LinearLayout> </layout>
Include
ActivityDetailEditBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_detail_edit);
mBinding.setBook(book); mBinding.setPresenter(this); mBinding.content.setPublishers(new ObservableArrayList<Publisher>());
Expression Chain<layout ...> <data> <import type="android.view.View" /> ... </data> <LinearLayout ...> <ImageView ... android:id="@+id/image_cover" android:visibility="@{book.available ? View.VISIBLE : View.INVISIBLE}" /> <RadioGroup ... android:id="@+id/radio_group_media_type" android:visibility="@{imageCover.visibility}" /> <RatingBar android:visibility="@{radioGroupMediaType.visibility}" android:id=“@+id/rating_book"> <CheckBox ... android:id="@+id/check_available" android:checked="@={book.available}" />
Ouvindo coisas… :)
binding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i) { if (i == br.com.nglauber.uidemo.BR.name){ Product p = (Product)observable; Log.d("NGVL", p.getName()); } } });
Ouvindo coisas… :)
Use para animações
binding.addOnRebindCallback(new OnRebindCallback() { @Override public boolean onPreBind(ViewDataBinding binding) { return super.onPreBind(binding); }
@Override public void onCanceled(ViewDataBinding binding) { super.onCanceled(binding); }
@Override public void onBound(ViewDataBinding binding) { super.onBound(binding); } });
Aplicando no seu projeto
1. Remova os findViewById’s :)
2. Faça o Binding dos seus objetos.
3. Use os callbacks (eventos) de UI.
4. Aproveite os objetos observáveis.
5. Two-way Data Binding é vida! ♥
Boas práticas
• Não coloque lógica de negócios na tela. Apenas lógica de tela.
• Os eventos disparados executarão na UI Thread.
• Simplifique a lógica de UI (não coloque expressões complicadas, crie métodos para isso.
• Considere utilizar um ViewModel.
• Leve os BinderAdapters com você :) Você só vai precisar escrevê-los uma vez. // TODO: Fazer uma lib para isso :p
Referências
• Data Binding -- Write Apps Faster (Android Dev Summit 2015)https://www.youtube.com/watch?v=NBbeQMOcnZ0
• Data Binding Documentation (pode melhorar né…?)http://developer.android.com/tools/data-binding/guide.html
• Nelson Glauber Blog http://www.nglauber.com.br/2016/05/android-data-binding.html
• Advanced Data Binding (Google I/O 2016)https://www.youtube.com/watch?v=DAmMN7m3wLU
Dúvidas?
@nglauber
+NelsonGlauber
www.nglauber.com.br
Obrigado!