Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data binding #15

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
31 changes: 15 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Android app that leverages the [OpenLibrary API](https://openlibrary.org/develop

## Overview

I've forked this repository in order to demonstrate refactoring of legacy code base into the latest used libraries + architectural patterns, right now.

I'll be adding more wiki as I integrate the Suggested Extensions and Milestones

The app does the following:

1. Fetch the books from the [OpenLibrary Search API](https://openlibrary.org/dev/docs/api/search) in JSON format
Expand All @@ -20,21 +24,16 @@ To achieve this, there are four different components in this app:
3. `BookAdapter` - Responsible for mapping each `Book` to a particular view layout
4. `BookListActivity` - Responsible for fetching and deserializing the data and configuring the adapter

## Usage
This app is intended to be the base project on top of which new features can be added. To use it, clone the project and import it using the following steps:

![Imgur](http://i.imgur.com/joPKoTk.gif)

## Suggested Extensions

1. Use SearchView to search for books with a title
2. Show ProgressBar before each network request
3. Add a detail view to display more information about the selected book from the list
4. Use a share intent to recommend a book to friends

## Libraries

This app leverages two third-party libraries:

* [Android AsyncHTTPClient](http://loopj.com/android-async-http/) - For asynchronous network requests
* [Picasso](http://square.github.io/picasso/) - For remote image loading
- [ ] Use SearchView to search for books with a title
- [ ] Show ProgressBar before each network request
- [ ] Add a detail view to display more information about the selected book from the list
- [ ] Use a share intent to recommend a book to friends

## Milestones
- [x] Add Retrofit
- [x] Add Data Binding
- [ ] Add RxJava
- [ ] Add Dagger
- [ ] Update to Material Design 2.0
25 changes: 20 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,29 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField("String", "BASE_URL", "$rootProject.base_url")
}

debug {
buildConfigField("String", "BASE_URL", "$rootProject.base_url")
}
}

dataBinding {
enabled = true
}
}

dependencies {
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.loopj.android:android-async-http:1.4.9'
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1'
implementation "com.android.support:appcompat-v7:$rootProject.appcompat_version"
implementation "com.android.support:recyclerview-v7:$rootProject.appcompat_version"

implementation "com.github.bumptech.glide:glide:$rootProject.glide_version"
annotationProcessor "com.github.bumptech.glide:compiler:$rootProject.glide_version"

implementation "com.squareup.retrofit2:retrofit:$rootProject.retrofit_version"
implementation "com.squareup.okhttp3:okhttp:$rootProject.okhttp_version"
implementation "com.google.code.gson:gson:$rootProject.gson_version"
implementation "com.squareup.retrofit2:converter-gson:$rootProject.retrofit_version"
implementation "com.squareup.okhttp3:logging-interceptor:$rootProject.okhttp_version"
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.codepath.android.booksearch.activities;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
Expand All @@ -8,6 +9,7 @@
import android.widget.TextView;

import com.codepath.android.booksearch.R;
import com.codepath.android.booksearch.databinding.BookDetailBinding;

public class BookDetailActivity extends AppCompatActivity {
private ImageView ivBookCover;
Expand All @@ -17,11 +19,8 @@ public class BookDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_detail);
// Fetch views
ivBookCover = (ImageView) findViewById(R.id.ivBookCover);
tvTitle = (TextView) findViewById(R.id.tvTitle);
tvAuthor = (TextView) findViewById(R.id.tvAuthor);
BookDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_book_detail);

// Extract book object from intent extras

Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,70 @@
package com.codepath.android.booksearch.activities;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;

import com.codepath.android.booksearch.R;
import com.codepath.android.booksearch.adapters.BookAdapter;
import com.codepath.android.booksearch.databinding.BookListingBinding;
import com.codepath.android.booksearch.models.Book;
import com.codepath.android.booksearch.net.BookClient;
import com.loopj.android.http.JsonHttpResponseHandler;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.codepath.android.booksearch.models.converters.BookConverter;
import com.codepath.android.booksearch.models.remote.BookQueryResponse;
import com.codepath.android.booksearch.remote.retrofitconfig.ApiCallback;
import com.codepath.android.booksearch.remote.BookClient;

import java.util.ArrayList;

import cz.msebera.android.httpclient.Header;
import java.util.List;


public class BookListActivity extends AppCompatActivity {
private RecyclerView rvBooks;
private BookAdapter bookAdapter;
private BookClient client;
private ArrayList<Book> abooks;

private BookListingBinding binding;

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

rvBooks = (RecyclerView) findViewById(R.id.rvBooks);
abooks = new ArrayList<>();
binding = DataBindingUtil.setContentView(this, R.layout.activity_book_list);

// initialize the adapter
bookAdapter = new BookAdapter(this, abooks);
abooks = new ArrayList<>();

// attach the adapter to the RecyclerView
rvBooks.setAdapter(bookAdapter);
bookAdapter = new BookAdapter(abooks);
binding.rvBooks.setAdapter(bookAdapter);
binding.rvBooks.setLayoutManager(new LinearLayoutManager(this));

// Set layout manager to position the items
rvBooks.setLayoutManager(new LinearLayoutManager(this));
client = new BookClient();

// Fetch the data remotely
fetchBooks("Oscar Wilde");
}

// Executes an API call to the OpenLibrary search endpoint, parses the results
// Converts them into an array of book objects and adds them to the adapter
private void fetchBooks(String query) {
client = new BookClient();
client.getBooks(query, new JsonHttpResponseHandler() {
client.getBooks(query, new ApiCallback<BookQueryResponse>() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
try {
JSONArray docs;
if(response != null) {
// Get the docs json array
docs = response.getJSONArray("docs");
// Parse json array into array of model objects
final ArrayList<Book> books = Book.fromJson(docs);
// Remove all books from the adapter
abooks.clear();
// Load model objects into the adapter
for (Book book : books) {
abooks.add(book); // add book through the adapter
}
bookAdapter.notifyDataSetChanged();
}
} catch (JSONException e) {
// Invalid JSON format, show appropriate error.
e.printStackTrace();
}
public void onSuccess(BookQueryResponse response) {
List<Book> books = BookConverter.getBooks(response);
abooks.clear();

// Load model objects into the adapter
abooks.addAll(books);
bookAdapter.notifyDataSetChanged();
}

@Override
public void onFailure(int code, String response) {
// todo: handle this
}

@Override
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
super.onFailure(statusCode, headers, responseString, throwable);
public void onFailure(Throwable t) {
//todo: show internet not available
}
});
}
Expand All @@ -105,4 +90,10 @@ public boolean onOptionsItemSelected(MenuItem item) {

return super.onOptionsItemSelected(item);
}

@Override
protected void onDestroy() {
super.onDestroy();
client.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -1,88 +1,55 @@
package com.codepath.android.booksearch.adapters;

import android.content.Context;
import android.net.Uri;
import android.databinding.DataBindingUtil;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.codepath.android.booksearch.GlideApp;
import com.codepath.android.booksearch.MyAppGlideModule;
import com.codepath.android.booksearch.R;
import com.codepath.android.booksearch.databinding.BookAdapterBinding;
import com.codepath.android.booksearch.models.Book;

import java.util.ArrayList;
import java.util.List;

public class BookAdapter extends RecyclerView.Adapter<BookAdapter.ViewHolder> {
private List<Book> mBooks;
private Context mContext;

// View lookup cache
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView ivCover;
public TextView tvTitle;
public TextView tvAuthor;

public ViewHolder(View itemView) {
// Stores the itemView in a public final member variable that can be used
// to access the context from any ViewHolder instance.
super(itemView);

ivCover = (ImageView)itemView.findViewById(R.id.ivBookCover);
tvTitle = (TextView)itemView.findViewById(R.id.tvTitle);
tvAuthor = (TextView)itemView.findViewById(R.id.tvAuthor);
}
}

public BookAdapter(Context context, ArrayList<Book> aBooks) {
public BookAdapter(ArrayList<Book> aBooks) {
mBooks = aBooks;
mContext = context;
}

// Usually involves inflating a layout from XML and returning the holder
@Override
public BookAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
LayoutInflater inflater = LayoutInflater.from(parent.getContext());

// Inflate the custom layout
View bookView = inflater.inflate(R.layout.item_book, parent, false);

// Return a new holder instance
BookAdapter.ViewHolder viewHolder = new BookAdapter.ViewHolder(bookView);
return viewHolder;
BookAdapterBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_book, parent, false);
return new BookAdapter.ViewHolder(binding);
}


// Involves populating data into the item through holder
@Override
public void onBindViewHolder(BookAdapter.ViewHolder viewHolder, int position) {
// Get the data model based on position
Book book = mBooks.get(position);

// Populate data into the template view using the data object
viewHolder.tvTitle.setText(book.getTitle());
viewHolder.tvAuthor.setText(book.getAuthor());
GlideApp.with(getContext())
.load(Uri.parse(book.getCoverUrl()))
.placeholder(R.drawable.ic_nocover)
.into(viewHolder.ivCover);
// Return the completed view to render on screen
viewHolder.setData(book);
}

// Returns the total count of items in the list
@Override
public int getItemCount() {
return mBooks.size();
}

// Easy access to the context object in the recyclerview
private Context getContext() {
return mContext;
public class ViewHolder extends RecyclerView.ViewHolder {

private BookAdapterBinding binding;

public ViewHolder(BookAdapterBinding binding) {
super(binding.getRoot());
this.binding = binding;
}

public void setData(Book book) {
binding.setViewModel(book);
binding.executePendingBindings();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codepath.android.booksearch.databinding;

import android.databinding.BindingAdapter;
import android.net.Uri;
import android.widget.ImageView;

import com.codepath.android.booksearch.GlideApp;

public class DataAdapter {

@BindingAdapter("imageUrl")
public static void setImageUrl(ImageView iv, String url) {
GlideApp.with(iv.getContext()).load(Uri.parse(url)).into(iv);
}
}
Loading