Dominando o Data Binding no Android

4.718 visualizações

Publicada em

Palestra sobre Data Binding no Android apresentada no DevFest Nordeste (devfestne.com.br) nos dia 22 de outubro de 2016.

Publicada em: Software
0 comentários
1 gostou
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
4.718
No SlideShare
0
A partir de incorporações
0
Número de incorporações
4.234
Ações
Compartilhamentos
0
Downloads
13
Comentários
0
Gostaram
1
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Dominando o Data Binding no Android

  1. 1. Dominando o Data Binding
 no Android +Nelson Glauber @nglauber www.nglauber.com.br
  2. 2. @nglauber +NelsonGlauber www.nglauber.com.br
  3. 3. Por que Data Binding?
  4. 4. 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
  5. 5. Estudo de caso
  6. 6. 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... }
  7. 7. 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());
  8. 8. 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);
  9. 9. Book book = (Book)getIntent().getSerializableExtra(EXTRA_BOOK); ActivityDetailViewBinding mBinding =
 DataBindingUtil.setContentView(this, R.layout.activity_detail_view); mBinding.setBook(book);
  10. 10. Conceitos básicos
  11. 11. Configuração Passo 1… android { ... dataBinding { enabled true } }
  12. 12. …e pronto!
  13. 13. 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>
  14. 14. 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
  15. 15. 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; ...
  16. 16. Mapeando objetos na UI
  17. 17. 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());
  18. 18. Book book = (Book)getIntent().getSerializableExtra(EXTRA_BOOK); ActivityDetailViewBinding mBinding =
 DataBindingUtil.setContentView(this, R.layout.activity_detail_view); mBinding.setBook(book);
  19. 19. <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>
  20. 20. <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>
  21. 21. <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>
  22. 22. Tá pronto? Pode rodar???
  23. 23. NÃO!
  24. 24. <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; }
  25. 25. Binding Adapters #1
  26. 26. 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
  27. 27. 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
  28. 28. <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"/>
  29. 29. Fragments e Adapters
  30. 30. 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(); }
  31. 31. 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>
  32. 32. BookViewHolder.java public static class BookViewHolder extends RecyclerView.ViewHolder { ItemBookBinding binding; public BookViewHolder(ItemBookBinding binding) { super(binding.getRoot()); this.binding = binding; } }
  33. 33. @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
  34. 34. E se os dados mudarem?
  35. 35. 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
  36. 36. public class Book implements Observable { // Copie as coisas do BaseObservable :) }
  37. 37. 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); } } }
  38. 38. 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); } }
  39. 39. Two-way Data Binding
  40. 40. <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>
  41. 41. <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>
  42. 42. <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>
  43. 43. Binding Adapters #2
  44. 44. 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
  45. 45. 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
  46. 46. Tratamento de eventos
  47. 47. <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
  48. 48. @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
  49. 49. <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>
  50. 50. 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); } } }
  51. 51. 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); } }
  52. 52. <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&lt;br.com.nglauber.livrosfirebase.model.Publisher&gt;" /> </data>
 <LinearLayout ...> <Spinner ... android:entries="@{publishers}" android:selection="@{publishers.indexOf(book.publisher)}" android:onItemSelected="@{(p, v, pos, id)->book.setPublisher(publishers[pos])}" /> </LinearLayout> </layout>
  53. 53. DetailEditActivity.java ObservableList<Publisher> publishers = loadListFromSomewhere(); mBinding.setPublishers(publishers);
  54. 54. <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>
  55. 55. Pronto!
  56. 56. http://jakewharton.github.io/butterknife/
  57. 57. Binding Adapters #3
  58. 58. 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`}" .../>
  59. 59. Mas se tiver um “set” pode usar! <android.support.v4.widget.DrawerLayout ... app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}"/>
  60. 60. Miscellaneous
  61. 61. Expression Language <TextView ... android:text="@{user.displayName ?? user.lastName}"/> <TextView ... android:text="@{`User: ` + user.name}”/> <LinearLayout ... android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"/>
  62. 62. 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}" />
  63. 63. 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);
  64. 64. 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>
  65. 65. Include ActivityDetailEditBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_detail_edit); mBinding.setBook(book); mBinding.setPresenter(this); mBinding.content.setPublishers(new ObservableArrayList<Publisher>());
  66. 66. 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}" />
  67. 67. 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()); } } });
  68. 68. 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); } });
  69. 69. 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! ♥
  70. 70. 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
  71. 71. 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
  72. 72. Dúvidas?
  73. 73. @nglauber +NelsonGlauber www.nglauber.com.br Obrigado!

×