diff --git a/README.md b/README.md index 534ab26..3ff04c4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/app/build.gradle b/app/build.gradle index 3827b78..a6fbac2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } diff --git a/app/src/main/java/com/codepath/android/booksearch/activities/BookDetailActivity.java b/app/src/main/java/com/codepath/android/booksearch/activities/BookDetailActivity.java index cc88f92..f561039 100644 --- a/app/src/main/java/com/codepath/android/booksearch/activities/BookDetailActivity.java +++ b/app/src/main/java/com/codepath/android/booksearch/activities/BookDetailActivity.java @@ -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; @@ -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; @@ -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 diff --git a/app/src/main/java/com/codepath/android/booksearch/activities/BookListActivity.java b/app/src/main/java/com/codepath/android/booksearch/activities/BookListActivity.java index 82cd792..2851f39 100644 --- a/app/src/main/java/com/codepath/android/booksearch/activities/BookListActivity.java +++ b/app/src/main/java/com/codepath/android/booksearch/activities/BookListActivity.java @@ -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 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() { @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 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 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 } }); } @@ -105,4 +90,10 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + + @Override + protected void onDestroy() { + super.onDestroy(); + client.dispose(); + } } diff --git a/app/src/main/java/com/codepath/android/booksearch/adapters/BookAdapter.java b/app/src/main/java/com/codepath/android/booksearch/adapters/BookAdapter.java index db1cd2a..1e8b422 100644 --- a/app/src/main/java/com/codepath/android/booksearch/adapters/BookAdapter.java +++ b/app/src/main/java/com/codepath/android/booksearch/adapters/BookAdapter.java @@ -1,18 +1,12 @@ 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; @@ -20,69 +14,42 @@ public class BookAdapter extends RecyclerView.Adapter { private List 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 aBooks) { + public BookAdapter(ArrayList 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(); + } } } diff --git a/app/src/main/java/com/codepath/android/booksearch/databinding/DataAdapter.java b/app/src/main/java/com/codepath/android/booksearch/databinding/DataAdapter.java new file mode 100644 index 0000000..ddcf53d --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/databinding/DataAdapter.java @@ -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); + } +} diff --git a/app/src/main/java/com/codepath/android/booksearch/models/Book.java b/app/src/main/java/com/codepath/android/booksearch/models/Book.java index 0bb7d72..c4ff2cd 100644 --- a/app/src/main/java/com/codepath/android/booksearch/models/Book.java +++ b/app/src/main/java/com/codepath/android/booksearch/models/Book.java @@ -13,6 +13,12 @@ public class Book { private String author; private String title; + public Book(String openLibraryId, String author, String title) { + this.openLibraryId = openLibraryId; + this.author = author; + this.title = title; + } + public String getOpenLibraryId() { return openLibraryId; } @@ -29,62 +35,4 @@ public String getAuthor() { public String getCoverUrl() { return "http://covers.openlibrary.org/b/olid/" + openLibraryId + "-L.jpg?default=false"; } - - // Returns a Book given the expected JSON - public static Book fromJson(JSONObject jsonObject) { - Book book = new Book(); - try { - // Deserialize json into object fields - // Check if a cover edition is available - if (jsonObject.has("cover_edition_key")) { - book.openLibraryId = jsonObject.getString("cover_edition_key"); - } else if(jsonObject.has("edition_key")) { - final JSONArray ids = jsonObject.getJSONArray("edition_key"); - book.openLibraryId = ids.getString(0); - } - book.title = jsonObject.has("title_suggest") ? jsonObject.getString("title_suggest") : ""; - book.author = getAuthor(jsonObject); - } catch (JSONException e) { - e.printStackTrace(); - return null; - } - // Return new object - return book; - } - - // Return comma separated author list when there is more than one author - private static String getAuthor(final JSONObject jsonObject) { - try { - final JSONArray authors = jsonObject.getJSONArray("author_name"); - int numAuthors = authors.length(); - final String[] authorStrings = new String[numAuthors]; - for (int i = 0; i < numAuthors; ++i) { - authorStrings[i] = authors.getString(i); - } - return TextUtils.join(", ", authorStrings); - } catch (JSONException e) { - return ""; - } - } - - // Decodes array of book json results into business model objects - public static ArrayList fromJson(JSONArray jsonArray) { - ArrayList books = new ArrayList<>(jsonArray.length()); - // Process each result in json array, decode and convert to business - // object - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject bookJson; - try { - bookJson = jsonArray.getJSONObject(i); - } catch (Exception e) { - e.printStackTrace(); - continue; - } - Book book = Book.fromJson(bookJson); - if (book != null) { - books.add(book); - } - } - return books; - } } diff --git a/app/src/main/java/com/codepath/android/booksearch/models/converters/BookConverter.java b/app/src/main/java/com/codepath/android/booksearch/models/converters/BookConverter.java new file mode 100644 index 0000000..037dba6 --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/models/converters/BookConverter.java @@ -0,0 +1,42 @@ +package com.codepath.android.booksearch.models.converters; + +import android.text.TextUtils; + +import com.codepath.android.booksearch.models.Book; +import com.codepath.android.booksearch.models.remote.BookQueryResponse; + +import java.util.ArrayList; +import java.util.List; + +public class BookConverter { + public static List getBooks(BookQueryResponse response) { + List books = new ArrayList<>(); + + List docs = response.getDocs(); + for (BookQueryResponse.DocsBean doc : docs) { + String openLibraryId = doc.getCover_edition_key(); + + // we need to fetch openLibraryId, title and author + // from the query response + + if (TextUtils.isEmpty(openLibraryId)) { + List ids = doc.getEdition_key(); + + if (ids != null && !ids.isEmpty()) + openLibraryId = ids.get(0); + + } + + String titleSuggest = doc.getTitle_suggest(); + String title = TextUtils.isEmpty(titleSuggest) ? "" : titleSuggest; + + List authorNames = doc.getAuthor_name(); + String authors = TextUtils.join(", ", authorNames); + + Book book = new Book(openLibraryId, title, authors); + books.add(book); + } + + return books; + } +} diff --git a/app/src/main/java/com/codepath/android/booksearch/models/remote/BookQueryResponse.java b/app/src/main/java/com/codepath/android/booksearch/models/remote/BookQueryResponse.java new file mode 100644 index 0000000..4c6f169 --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/models/remote/BookQueryResponse.java @@ -0,0 +1,416 @@ +package com.codepath.android.booksearch.models.remote; + +import java.util.List; + +public class BookQueryResponse { + + private int start; + private int num_found; + private int numFound; + private List docs; + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getNum_found() { + return num_found; + } + + public void setNum_found(int num_found) { + this.num_found = num_found; + } + + public int getNumFound() { + return numFound; + } + + public void setNumFound(int numFound) { + this.numFound = numFound; + } + + public List getDocs() { + return docs; + } + + public void setDocs(List docs) { + this.docs = docs; + } + + public static class DocsBean { + + private String title_suggest; + private int cover_i; + private boolean has_fulltext; + private String title; + private String lending_identifier_s; + private String ia_collection_s; + private int first_publish_year; + private String type; + private int ebook_count_i; + private int edition_count; + private String key; + private boolean public_scan_b; + private int last_modified_i; + private String lending_edition_s; + private String cover_edition_key; + private String printdisabled_s; + private List edition_key; + private List isbn; + private List text; + private List author_name; + private List contributor; + private List ia_loaded_id; + private List seed; + private List oclc; + private List ia; + private List author_key; + private List subject; + private List publish_place; + private List ia_box_id; + private List id_goodreads; + private List author_alternative_name; + private List id_overdrive; + private List publisher; + private List language; + private List lccn; + private List id_librarything; + private List person; + private List publish_year; + private List place; + private List time; + private List publish_date; + + public String getTitle_suggest() { + return title_suggest; + } + + public void setTitle_suggest(String title_suggest) { + this.title_suggest = title_suggest; + } + + public int getCover_i() { + return cover_i; + } + + public void setCover_i(int cover_i) { + this.cover_i = cover_i; + } + + public boolean isHas_fulltext() { + return has_fulltext; + } + + public void setHas_fulltext(boolean has_fulltext) { + this.has_fulltext = has_fulltext; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getLending_identifier_s() { + return lending_identifier_s; + } + + public void setLending_identifier_s(String lending_identifier_s) { + this.lending_identifier_s = lending_identifier_s; + } + + public String getIa_collection_s() { + return ia_collection_s; + } + + public void setIa_collection_s(String ia_collection_s) { + this.ia_collection_s = ia_collection_s; + } + + public int getFirst_publish_year() { + return first_publish_year; + } + + public void setFirst_publish_year(int first_publish_year) { + this.first_publish_year = first_publish_year; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getEbook_count_i() { + return ebook_count_i; + } + + public void setEbook_count_i(int ebook_count_i) { + this.ebook_count_i = ebook_count_i; + } + + public int getEdition_count() { + return edition_count; + } + + public void setEdition_count(int edition_count) { + this.edition_count = edition_count; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public boolean isPublic_scan_b() { + return public_scan_b; + } + + public void setPublic_scan_b(boolean public_scan_b) { + this.public_scan_b = public_scan_b; + } + + public int getLast_modified_i() { + return last_modified_i; + } + + public void setLast_modified_i(int last_modified_i) { + this.last_modified_i = last_modified_i; + } + + public String getLending_edition_s() { + return lending_edition_s; + } + + public void setLending_edition_s(String lending_edition_s) { + this.lending_edition_s = lending_edition_s; + } + + public String getCover_edition_key() { + return cover_edition_key; + } + + public void setCover_edition_key(String cover_edition_key) { + this.cover_edition_key = cover_edition_key; + } + + public String getPrintdisabled_s() { + return printdisabled_s; + } + + public void setPrintdisabled_s(String printdisabled_s) { + this.printdisabled_s = printdisabled_s; + } + + public List getEdition_key() { + return edition_key; + } + + public void setEdition_key(List edition_key) { + this.edition_key = edition_key; + } + + public List getIsbn() { + return isbn; + } + + public void setIsbn(List isbn) { + this.isbn = isbn; + } + + public List getText() { + return text; + } + + public void setText(List text) { + this.text = text; + } + + public List getAuthor_name() { + return author_name; + } + + public void setAuthor_name(List author_name) { + this.author_name = author_name; + } + + public List getContributor() { + return contributor; + } + + public void setContributor(List contributor) { + this.contributor = contributor; + } + + public List getIa_loaded_id() { + return ia_loaded_id; + } + + public void setIa_loaded_id(List ia_loaded_id) { + this.ia_loaded_id = ia_loaded_id; + } + + public List getSeed() { + return seed; + } + + public void setSeed(List seed) { + this.seed = seed; + } + + public List getOclc() { + return oclc; + } + + public void setOclc(List oclc) { + this.oclc = oclc; + } + + public List getIa() { + return ia; + } + + public void setIa(List ia) { + this.ia = ia; + } + + public List getAuthor_key() { + return author_key; + } + + public void setAuthor_key(List author_key) { + this.author_key = author_key; + } + + public List getSubject() { + return subject; + } + + public void setSubject(List subject) { + this.subject = subject; + } + + public List getPublish_place() { + return publish_place; + } + + public void setPublish_place(List publish_place) { + this.publish_place = publish_place; + } + + public List getIa_box_id() { + return ia_box_id; + } + + public void setIa_box_id(List ia_box_id) { + this.ia_box_id = ia_box_id; + } + + public List getId_goodreads() { + return id_goodreads; + } + + public void setId_goodreads(List id_goodreads) { + this.id_goodreads = id_goodreads; + } + + public List getAuthor_alternative_name() { + return author_alternative_name; + } + + public void setAuthor_alternative_name(List author_alternative_name) { + this.author_alternative_name = author_alternative_name; + } + + public List getId_overdrive() { + return id_overdrive; + } + + public void setId_overdrive(List id_overdrive) { + this.id_overdrive = id_overdrive; + } + + public List getPublisher() { + return publisher; + } + + public void setPublisher(List publisher) { + this.publisher = publisher; + } + + public List getLanguage() { + return language; + } + + public void setLanguage(List language) { + this.language = language; + } + + public List getLccn() { + return lccn; + } + + public void setLccn(List lccn) { + this.lccn = lccn; + } + + public List getId_librarything() { + return id_librarything; + } + + public void setId_librarything(List id_librarything) { + this.id_librarything = id_librarything; + } + + public List getPerson() { + return person; + } + + public void setPerson(List person) { + this.person = person; + } + + public List getPublish_year() { + return publish_year; + } + + public void setPublish_year(List publish_year) { + this.publish_year = publish_year; + } + + public List getPlace() { + return place; + } + + public void setPlace(List place) { + this.place = place; + } + + public List getTime() { + return time; + } + + public void setTime(List time) { + this.time = time; + } + + public List getPublish_date() { + return publish_date; + } + + public void setPublish_date(List publish_date) { + this.publish_date = publish_date; + } + } +} diff --git a/app/src/main/java/com/codepath/android/booksearch/net/BookClient.java b/app/src/main/java/com/codepath/android/booksearch/net/BookClient.java deleted file mode 100644 index ba75e69..0000000 --- a/app/src/main/java/com/codepath/android/booksearch/net/BookClient.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.codepath.android.booksearch.net; - -import com.loopj.android.http.AsyncHttpClient; -import com.loopj.android.http.JsonHttpResponseHandler; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - -public class BookClient { - private static final String API_BASE_URL = "http://openlibrary.org/"; - private AsyncHttpClient client; - - public BookClient() { - this.client = new AsyncHttpClient(); - } - - private String getApiUrl(String relativeUrl) { - return API_BASE_URL + relativeUrl; - } - - // Method for accessing the search API - public void getBooks(final String query, JsonHttpResponseHandler handler) { - try { - String url = getApiUrl("search.json?q="); - client.get(url + URLEncoder.encode(query, "utf-8"), handler); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - } -} diff --git a/app/src/main/java/com/codepath/android/booksearch/remote/BookClient.java b/app/src/main/java/com/codepath/android/booksearch/remote/BookClient.java new file mode 100644 index 0000000..b097b96 --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/remote/BookClient.java @@ -0,0 +1,46 @@ +package com.codepath.android.booksearch.remote; + +import com.codepath.android.booksearch.models.remote.BookQueryResponse; +import com.codepath.android.booksearch.remote.retrofitconfig.ApiCallback; +import com.codepath.android.booksearch.remote.retrofitconfig.ApiClient; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.util.ArrayList; +import java.util.List; +import retrofit2.Call; + +public class BookClient { + + BookService bookService = ApiClient.getRetrofit().create(BookService.class); + private static Gson gson; + + public static Gson getGson() { + if (gson == null) { + gson = new GsonBuilder().setLenient().create(); + } + + return gson; + } + + private List requestCalls = new ArrayList<>(); + + public BookClient() { + } + // Method for accessing the search API + + public void getBooks(final String query, ApiCallback handler) { + Call queryCall = bookService.queryForBooks(query); + queryCall.enqueue(handler); + + + requestCalls.add(queryCall); + } + + public void dispose() { + for (Call call : requestCalls) { + if(!call.isCanceled()) + call.cancel(); + + } + } +} diff --git a/app/src/main/java/com/codepath/android/booksearch/remote/BookService.java b/app/src/main/java/com/codepath/android/booksearch/remote/BookService.java new file mode 100644 index 0000000..c725842 --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/remote/BookService.java @@ -0,0 +1,12 @@ +package com.codepath.android.booksearch.remote; + +import com.codepath.android.booksearch.models.remote.BookQueryResponse; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface BookService { + @GET(EndPoints.PATH_QUERY_BOOKS) + Call queryForBooks(@Query("q") String queryParam); +} diff --git a/app/src/main/java/com/codepath/android/booksearch/remote/EndPoints.java b/app/src/main/java/com/codepath/android/booksearch/remote/EndPoints.java new file mode 100644 index 0000000..3879306 --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/remote/EndPoints.java @@ -0,0 +1,5 @@ +package com.codepath.android.booksearch.remote; + +public interface EndPoints { + String PATH_QUERY_BOOKS = "/search.json"; +} diff --git a/app/src/main/java/com/codepath/android/booksearch/remote/retrofitconfig/ApiCallback.java b/app/src/main/java/com/codepath/android/booksearch/remote/retrofitconfig/ApiCallback.java new file mode 100644 index 0000000..d3784ff --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/remote/retrofitconfig/ApiCallback.java @@ -0,0 +1,38 @@ +package com.codepath.android.booksearch.remote.retrofitconfig; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public abstract class ApiCallback implements Callback { + public abstract void onSuccess(T response); + + public abstract void onFailure(int code, String response); + + public abstract void onFailure(Throwable t); + + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + T body = response.body(); + onSuccess(body); + } else { + try { + int code = response.code(); + String errorBody = null; + + if (response.errorBody() != null) + errorBody = response.errorBody().string(); + + onFailure(code, errorBody); + } catch (Exception e) { + onFailure(e); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + onFailure(t); + } +} diff --git a/app/src/main/java/com/codepath/android/booksearch/remote/retrofitconfig/ApiClient.java b/app/src/main/java/com/codepath/android/booksearch/remote/retrofitconfig/ApiClient.java new file mode 100644 index 0000000..5eb0007 --- /dev/null +++ b/app/src/main/java/com/codepath/android/booksearch/remote/retrofitconfig/ApiClient.java @@ -0,0 +1,43 @@ +package com.codepath.android.booksearch.remote.retrofitconfig; + +import com.codepath.android.booksearch.BuildConfig; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ApiClient { + private static Retrofit retrofit = null; + + // this will work as our singleton object + public static Retrofit getRetrofit() { + if (retrofit == null) { + initRetrofit(); + } + + return retrofit; + } + + private static void initRetrofit() { + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE); + + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .addNetworkInterceptor(loggingInterceptor) + .build(); + + + Gson gson = new GsonBuilder().setLenient().create(); + + retrofit = new Retrofit.Builder() + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .baseUrl(BuildConfig.BASE_URL) + .build(); + } + + +} diff --git a/app/src/main/res/layout/activity_book_detail.xml b/app/src/main/res/layout/activity_book_detail.xml index 5343458..034eb03 100644 --- a/app/src/main/res/layout/activity_book_detail.xml +++ b/app/src/main/res/layout/activity_book_detail.xml @@ -1,30 +1,37 @@ - + - + - + - + - \ No newline at end of file + + + + + + diff --git a/app/src/main/res/layout/activity_book_list.xml b/app/src/main/res/layout/activity_book_list.xml index b676ee1..7cf8f83 100644 --- a/app/src/main/res/layout/activity_book_list.xml +++ b/app/src/main/res/layout/activity_book_list.xml @@ -1,12 +1,16 @@ - + - + + + android:layout_height="match_parent"> - + + + diff --git a/app/src/main/res/layout/item_book.xml b/app/src/main/res/layout/item_book.xml index 3d9347b..839693a 100644 --- a/app/src/main/res/layout/item_book.xml +++ b/app/src/main/res/layout/item_book.xml @@ -1,31 +1,44 @@ - + - + - + + - - \ No newline at end of file + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index c06df43..25dc30f 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { google() } dependencies { + apply from: 'config.gradle' classpath 'com.android.tools.build:gradle:3.1.2' // NOTE: Do not place your application dependencies here; they belong diff --git a/config.gradle b/config.gradle new file mode 100644 index 0000000..480a9b3 --- /dev/null +++ b/config.gradle @@ -0,0 +1,9 @@ +ext{ + appcompat_version = "27.1.1" + glide_version = "4.7.1" + retrofit_version = "2.4.0" + okhttp_version = "3.11.0" + gson_version = "2.8.5" + + base_url = "\"http://openlibrary.org\"" +} \ No newline at end of file