В разделе "У меня возникли проблемы" выясните, почему пользовательский загрузчик не работает в основной активности. Может кто-то помочь мне с этим? Я понимаю, что BookLoader
не правильно. Но какое значение я могу передать с ним?
Нужно ли что-то возвращать в файле BookLoader
in BooKloader.java
?
MainActivity. java
package com.example.findbooks;
import androidx.appcompat.app.AppCompatActivity;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.content.Loader;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import static android.view.View.GONE;
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<Books>> {
/**
* Tag for the log messages
*/
public static final String LOG_TAG = MainActivity.class.getSimpleName();
private WordAdapter wordAdapter;
private static final int BOOK_LOADER_ID = 1;
Boolean isConnected;
private View circleProgressBar;
private String mUrlRequestGoogleBooks = "";//URL for books data from the Google Books API
private TextView mEmptyStateTextView;//TextView that is displayed when the list is empty
private SearchView mSearchViewField;//Search field
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
/**
* At the beginning check the connection with internet and save result to (boolean) variable isConnected
* Checking if network is available
* If TRUE - work with LoaderManager
* If FALSE - hide loading spinner and show emptyStateTextView
*/
checkConnection(cm);
// Find a reference to the {@link ListView} in the layout
ListView listView = findViewById(R.id.list);
// Create a new adapter that takes an empty list of books as input
wordAdapter = new WordAdapter(this,new ArrayList<Books>());
// Set the adapter on the {@link ListView}
// so the list can be populated in the user interface
listView.setAdapter(wordAdapter);
// Find a reference to the empty view
mEmptyStateTextView = findViewById(R.id.empty_view);
listView.setEmptyView(mEmptyStateTextView);
// Circle progress
circleProgressBar = findViewById(R.id.loading_spinner);
// Search button
Button mSearchButton = findViewById(R.id.search_button);
// Search field
mSearchViewField = findViewById(R.id.search_view_field);
mSearchViewField.onActionViewExpanded();
mSearchViewField.setIconified(true);
mSearchViewField.setQueryHint("Enter a book title");
if(isConnected){
LoaderManager loaderManager = getLoaderManager();
// Initialize the loader.
loaderManager.initLoader(BOOK_LOADER_ID, null, this);
}else {
Log.i(LOG_TAG, "INTERNET connection status: " + false + ". Sorry dude, no internet - no data :(");
circleProgressBar.setVisibility(GONE);
mEmptyStateTextView.setText("No Internet");
}
// Set an item click listener on the Search Button, which sends a request to
// Google Books API based on value from Search View
mSearchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkConnection(cm);
if (isConnected){
updateQueryUrl(mSearchViewField.getQuery().toString());
restartLoader();
Log.i(LOG_TAG,"Search value: " + mSearchViewField.getQuery().toString());
}else {
wordAdapter.clear();
mEmptyStateTextView.setVisibility(View.VISIBLE);
mEmptyStateTextView.setText("No Internet connection.");// ...and display message: "No internet connection."
}
}
});
// Set an item click listener on the ListView, which sends an intent to a web browser
// to open a website with more information about the selected book.
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Find the current book that was clicked on
Books currentBook = wordAdapter.getItem(position);
// Convert the String URL into a URI object (to pass into the Intent constructor)
assert currentBook != null;
Uri bookUri = Uri.parse(currentBook.getURI());
// Create a new intent to view the earthquake URI
Intent websiteIntent = new Intent(Intent.ACTION_VIEW, bookUri);
// Send the intent to launch a new activity
startActivity(websiteIntent);
}
});
}
private String updateQueryUrl(String searchValue) {
if (searchValue.contains(" ")) {
searchValue = searchValue.replace(" ", "+");
}
StringBuilder sb = new StringBuilder();
sb.append("https://www.googleapis.com/books/v1/volumes?q=").append(searchValue).append("&filter=paid-ebooks&maxResults=40");
mUrlRequestGoogleBooks = sb.toString();
return mUrlRequestGoogleBooks;
}
@Override
public Loader<List<Books>> onCreateLoader(int id, Bundle args) {
Log.i("There is no instance", ": Created new one loader at the beginning!");
// Create a new loader for the given URL
updateQueryUrl(mSearchViewField.getQuery().toString());
return new BookLoader(this, mUrlRequestGoogleBooks);
}
@Override
public void onLoadFinished(android.content.Loader<List<Books>> loader, List<Books> data) {
View circleProgressBar = findViewById(R.id.loading_spinner);
circleProgressBar.setVisibility(GONE);
mEmptyStateTextView.setText("No Books");
Log.i(LOG_TAG, ": Books has been moved to adapter's data set. This will trigger the ListView to update!");
wordAdapter.clear();
if(data != null && !data.isEmpty()){
wordAdapter.addAll(data);
}
}
@Override
public void onLoaderReset(android.content.Loader<List<Books>> loader) {
// Loader reset, so we can clear out our existing data.
wordAdapter.clear();
Log.i(LOG_TAG, ": Loader reset, so we can clear out our existing data!");
}
public void restartLoader() {
mEmptyStateTextView.setVisibility(GONE);
circleProgressBar.setVisibility(View.VISIBLE);
getLoaderManager().restartLoader(BOOK_LOADER_ID, null, MainActivity.this);
}
public void checkConnection(ConnectivityManager connectivityManager) {
// Status of internet connection
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
if (activeNetwork != null && activeNetwork.isConnectedOrConnecting()) {
isConnected = true;
Log.i(LOG_TAG, "INTERNET connection status: " + (true) + ". It's time to play with LoaderManager :)");
} else {
isConnected = false;
}
}
}
Запрос. java
package com.example.findbooks;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Query {
private static final String LOG_TAG = Query.class.getSimpleName();
private Query() {
}
private static List<Books> getFromJson(String booksJSON){
if (TextUtils.isEmpty(booksJSON)) {
return null;
}
List<Books> books = new ArrayList<>();
try {
// Create a JSONObject from the JSON response string
JSONObject baseJsonResponse = new JSONObject(booksJSON);
Log.println(Log.INFO,LOG_TAG,booksJSON);
// Extract the JSONArray associated with the key called "features",
// which represents a list of features (or earthquakes).
JSONArray booksArray = baseJsonResponse.getJSONArray("items");
Log.println(Log.INFO,LOG_TAG,String.valueOf(booksArray));
// For each earthquake in the earthquakeArray, create an {@link Earthquake} object
for (int i = 0; i < booksArray.length(); i++) {
// Get a single earthquake at position i within the list of earthquakes
JSONObject currentbook = booksArray.getJSONObject(i);
Log.println(Log.INFO,LOG_TAG,String.valueOf(currentbook));
// For a given earthquake, extract the JSONObject associated with the
// key called "properties", which represents a list of all properties
// for that earthquake.
JSONObject volumeInfo = currentbook.getJSONObject("volumeInfo");
String author;
if(volumeInfo.isNull("authors")){
JSONArray authors = volumeInfo.getJSONArray("authors");
Log.println(Log.INFO,LOG_TAG,String.valueOf(authors));
// Check JSONArray Returns true if this object has no mapping for name or if it has a mapping whose value is NULL
if (!volumeInfo.isNull("authors")) {
// Get 1st element
author = (String) authors.get(0);
} else {
// assign info about missing info about author
author = "*** unknown author ***";
}
}else {
author = "*** missing info of authors ***";
}
// For a given book, extract the JSONObject associated with the
// key called "imageLinks", which represents a list of all cover
// images in a different size
JSONObject imageLinks = volumeInfo.getJSONObject("imageLinks");
Log.println(Log.INFO, LOG_TAG, String.valueOf(imageLinks));
// Extract the value for the key called "title"
String title = volumeInfo.getString("title");
// Extract the value for the key called "time"
String publishedDate = volumeInfo.getString("publishedDate");
String maturityRating = volumeInfo.getString("maturityRating");
// Extract the value for the key called "url"
String url = volumeInfo.getString("infoLink");
// Extract String URL of specific cover
String coverImageUrl = imageLinks.getString("smallThumbnail");
// Extract the value for the key called "smallThumbnail"
// Using REGEX and StringBuilder
StringBuilder stringBuilder = new StringBuilder();
Pattern p = Pattern.compile("id=(.*?)&");
Matcher m = p.matcher(coverImageUrl);
if (m.matches()) {
String id = m.group(1);
coverImageUrl = String.valueOf(stringBuilder.append("https://books.google.com/books/content/images/frontcover/").append(id).append("?fife=w300"));
} else {
Log.i(LOG_TAG, "Issue with cover");
}
// Create a new {@link Earthquake} object with the magnitude, location, time,
// and url from the JSON response.
Books booksItem = new Books(title, author, publishedDate,maturityRating, url,coverImageUrl);
// Add the new {@link Earthquake} to the list of earthquakes.
books.add(booksItem);
}
} catch (JSONException e) {
// If an error is thrown when executing any of the above statements in the "try" block,
// catch the exception here, so the app doesn't crash. Print a log message
// with the message from the exception.
Log.e("QueryUtils", "Problem parsing the BOOKS JSON results", e);
}
// Return the list of earthquakes
return books;
}
public static URL createUrl(String stringUrl){
URL url = null;
try {
url = new URL(stringUrl);
}catch (MalformedURLException e) {
Log.e(LOG_TAG, "Problem building the URL ", e);
}
return url;
}
private static String makeHttpRequest(URL url) throws IOException {
String jsonResponse = "";
// If the URL is null, then return early.
if (url == null) {
return jsonResponse;
}
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.setRequestMethod("GET");
urlConnection.connect();
// If the request was successful (response code 200),
// then read the input stream and parse the response.
if (urlConnection.getResponseCode() == 200) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
} else {
Log.e(LOG_TAG, "Error response code: " + urlConnection.getResponseCode());
}
} catch (IOException e) {
Log.e(LOG_TAG, "Problem retrieving the book JSON results.", e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (inputStream != null) {
// Closing the input stream could throw an IOException, which is why
// the makeHttpRequest(URL url) method signature specifies than an IOException
// could be thrown.
inputStream.close();
}
}
return jsonResponse;
}
private static String readFromStream(InputStream inputStream) throws IOException {
StringBuilder output = new StringBuilder();
if (inputStream != null) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
BufferedReader reader = new BufferedReader(inputStreamReader);
String line = reader.readLine();
while (line != null) {
output.append(line);
line = reader.readLine();
}
}
return output.toString();
}
public static List<Books> fetchBooksData(String requestUrl){
final int SLEEP_TIME_MILLIS = 2000;
// This action with sleeping is required for displaying circle progress bar
try {
Thread.sleep(SLEEP_TIME_MILLIS);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Create URL object
URL url = createUrl(requestUrl);
// Perform HTTP request to the URL and receive a JSON response back
String jsonResponse = null;
try {
jsonResponse = makeHttpRequest(url);
Log.i(LOG_TAG, "HTTP request: OK");
} catch (IOException e) {
Log.e(LOG_TAG, "Problem making the HTTP request.", e);
}
// Extract relevant fields from the JSON response and create a list of {@link Book}s
List<Books> listBooks = getFromJson(jsonResponse);
// Return the list of {@link Book}s
return listBooks;
}
}
WordAdapter
package com.example.findbooks;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.media.Image;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
public class WordAdapter extends ArrayAdapter<Books> {
private static final String LOG_TAG = WordAdapter.class.getSimpleName();
public WordAdapter(Activity context, ArrayList<Books> books){
super(context,0, books);
}
@SuppressLint("ViewHolder")
public View getView(int position, View convertView, ViewGroup parent){
View listItemView = convertView;
if (listItemView == null) {
listItemView = LayoutInflater.from(getContext()).inflate(
R.layout.list, parent, false);
}
final Books currentBook = getItem(position);
Log.i(LOG_TAG, "Item position: " + position);
//This is to get name of the book.
TextView bookNameTextView = listItemView.findViewById(R.id.bookName);
assert currentBook != null;
bookNameTextView.setText(currentBook.getBookName());
TextView authorNameTextView = listItemView.findViewById(R.id.authorName);
authorNameTextView.setText(currentBook.getAuthorName());
TextView pubYearTextView = listItemView.findViewById(R.id.pubYear);
pubYearTextView.setText(currentBook.getPubYear());
TextView maturityRatingTextView = listItemView.findViewById(R.id.maturityRating);
maturityRatingTextView.setText(currentBook.getMatureRating());
ImageView imageView = listItemView.findViewById(R.id.imageView);
Picasso.with(getContext()).load(currentBook.getImageURL()).into(imageView);
return listItemView;
}
}
Книги. java
package com.example.findbooks;
import android.media.Image;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
public class Books implements Parcelable {
public static final Parcelable.Creator<Books> CREATOR = new Parcelable.Creator<Books>(){
@Override
public Books createFromParcel(Parcel source) {
return new Books(source);
}
@Override
public Books[] newArray(int size) {
return new Books[size];
}
};
private String bookName;
private String authorName;
private String pubYear;
private String matureRating;
private String URI;
private String urlImageCover;
public Books(String bookName,String authorName, String pubYear,String matureRating,String URI,String urlImageCover){
this.authorName=authorName;
this.bookName=bookName;
this.pubYear=pubYear;
this.matureRating=matureRating;
this.URI=URI;
this.urlImageCover=urlImageCover;
}
protected Books(Parcel in) {
this.authorName = in.readString();
this.bookName = in.readString();
this.pubYear = in.readString();
this.matureRating = in.readString();
this.URI = in.readString();
this.urlImageCover = in.readString();
}
public String getBookName(){
return bookName;
}
public String getAuthorName(){
return authorName;
}
public String getPubYear(){
return pubYear;
}
public String getMatureRating(){
return matureRating;
}
public String getURI(){
return URI;
}
public String getImageURL(){
return urlImageCover;
}
@Override
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags){
dest.writeString(this.authorName);
dest.writeString(this.bookName);
dest.writeString(this.pubYear);
dest.writeString(this.matureRating);
dest.writeString(this.urlImageCover);
}
}
BookLoader. java
package com.example.findbooks;
import android.content.Context;
import android.util.Log;
import androidx.loader.content.AsyncTaskLoader;
import java.util.List;
public class BookLoader extends AsyncTaskLoader<List<Books>> {
private static final String LOG_TAG = BookLoader.class.getName();//Tag for log messages
private String mUrl;//Query URL
public BookLoader(Context context, String url) {
super(context);
mUrl = url;
Log.i(LOG_TAG, ": Loaded!");
}
@Override
protected void onStartLoading() {
forceLoad();
Log.i("On start loading", ": Force loaded!");
}
/**
* This is on a background thread.
*/
@Override
public List<Books> loadInBackground() {
if (mUrl == null) {
return null;
}
// Perform the network request, parse the response, and extract a list of books.
List<Books> books = Query.fetchBooksData(mUrl);
Log.i(LOG_TAG, ": Loaded in background!");
return books;
}
}