I'm new to Android and Firebase and I'm having this weird bug I don't understand. I have a RecyclerView that should display a list of Strings that have dates followed by a number. Now, I want to retrieve the data from Cloud Firestore and display it. For this purpose, I use an AsyncTaskLoader and in loadInBackground() I retrieve the data from Cloud Firestore. Now, when I start the activity, it shows the error message (and it will keep behaving this way no matter how many times I Hit the refresh button). However, if I turn off the screen and then turn it on, it shows the data the way I wish. The following is my code
public class MeasureListActivity extends AppCompatActivity implements
MeasuresAdapter.MeasuresAdapterOnClickHandler,
LoaderManager.LoaderCallbacks<String[]> {
private RecyclerView mRecyclerView;
private FirebaseAuth mAuth;
private MeasuresAdapter mMeasuresAdapter;
private TextView mErrorMessageDisplay;
private ProgressBar mLoadingIndicator;
private static final int MEASURES_LOADER_ID = 0;
#Override
public boolean onCreateOptionsMenu(Menu menu) {
/* Use AppCompatActivity's method getMenuInflater to get a handle on the menu inflater */
MenuInflater inflater = getMenuInflater();
/* Use the inflater's inflate method to inflate our menu layout to this menu */
inflater.inflate(R.menu.measures_list, menu);
/* Return true so that the menu is displayed in the Toolbar */
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_refresh) {
invalidateData();
getSupportLoaderManager().restartLoader(MEASURES_LOADER_ID, null, this);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_measure_list);
mAuth = FirebaseAuth.getInstance();
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview_measures);
mErrorMessageDisplay = (TextView) findViewById(R.id.tv_error_message_display);
LinearLayoutManager layoutManager
= new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
mMeasuresAdapter = new MeasuresAdapter(this);
mRecyclerView.setAdapter(mMeasuresAdapter);
mLoadingIndicator = (ProgressBar) findViewById(R.id.pb_loading_indicator);
int loaderId = MEASURES_LOADER_ID;
LoaderManager.LoaderCallbacks<String[]> callback = MeasureListActivity.this;
Bundle bundleForLoader = null;
getSupportLoaderManager().initLoader(loaderId, bundleForLoader, callback);
}
#Override
public Loader<String[]> onCreateLoader(int id, final Bundle loaderArgs) {
return new AsyncTaskLoader<String[]>(this) {
String[] mWMeasuresData = null;
#Override
protected void onStartLoading() {
if (mWMeasuresData != null) {
deliverResult(mWMeasuresData);
} else {
mLoadingIndicator.setVisibility(View.VISIBLE);
forceLoad();
}
}
private String[] aux;
#Override
public String[] loadInBackground() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("medidas").whereEqualTo("id_user", "3Aq3g0czkarT8GIbyESV").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
QuerySnapshot snapshot = task.getResult();
int tam = snapshot.getDocuments().size();
aux = new String[tam];
for (int i = 0; i < tam; i++) {
String temp = "";
DocumentSnapshot doc = snapshot.getDocuments().get(i);
temp += doc.get("fecha") + " ";
temp += doc.get("valor");
aux[i] = temp;
}
}
}
});
return aux;
}
public void deliverResult(String[] data) {
mWMeasuresData = data;
super.deliverResult(data);
}
};
}
#Override
public void onLoadFinished(Loader<String[]> loader, String[] data) {
mLoadingIndicator.setVisibility(View.INVISIBLE);
mMeasuresAdapter.setMeasuresData(data);
if (null == data) {
showErrorMessage();
} else {
showMeasuresDataView();
}
}
#Override
public void onLoaderReset(Loader<String[]> loader) {
}
private void invalidateData() {
mMeasuresAdapter.setMeasuresData(null);
}
private void showMeasuresDataView() {
/* First, make sure the error is invisible */
mErrorMessageDisplay.setVisibility(View.INVISIBLE);
/* Then, make sure the weather data is visible */
mRecyclerView.setVisibility(View.VISIBLE);
}
private void showErrorMessage() {
/* First, hide the currently visible data */
mRecyclerView.setVisibility(View.INVISIBLE);
/* Then, show the error */
mErrorMessageDisplay.setVisibility(View.VISIBLE);
}
#Override
public void onClick(String measure) {
Context context = this;
Class destinationClass = DetailMeasureActivity.class;
Intent intentToStartDetailActivity = new Intent(context, destinationClass);
intentToStartDetailActivity.putExtra(Intent.EXTRA_TEXT, measure);
startActivity(intentToStartDetailActivity);
}
}
Can you help me with this?...thanks in advance
First of all, I think that when you call this block of code:
mLoadingIndicator.setVisibility(View.VISIBLE);
forceLoad();
You should also hide the error message view.
Also, on a more serious note, you do not need AsyncTaskLoader to get your data from firestore, firebase firestore already handles background work for you.
To be able to understand fully where the problem is from, you need to understand you activity lifecycle and add logs to mark critical events that will help you to know which block of code is called at any point in time. So I will encourage you to add logs for when the data loading starts, finishes and when you are hiding a view or showing a view just to be sure that all methods are called and also add logs to your activity lifecycles.
Related
I am developing Android app which obtains information about restaurants from server and shows them in RecyclerView. When first package of information is obtained from server, everything works as expected, but, when I change search criteria and request new package of information from server, RecyclerView becomes blank. I used Toasts to debug what is coming from server and I am convinced that data is properly formatted. Also, variables that are used for accepting the data are also properly handled in code, according to my observations. Do you maybe know why my RecyclerView is empty when second package of data should be shown? Here is the code.
AfterLoginActivity.java
public class AfterLoginActivity extends AppCompatActivity {
/* interface main elements */
LinearLayout afterLoginLayout;
LinearLayout restaurantListLayout;
EditText restaurantEditText;
Button findRestaurantButton;
LoadingDialog loadingDialog;
AlphaAnimation loadingAnimation;
RecyclerView restaurantsRecyclerView;
int recycler_set = 0;
Button signOutButton;
GoogleSignInClient mGoogleSignInClient;
MyAdapter myAdapter = null;
/* server-client communication data */
public static String UploadUrl = "https://gdedakliknem.com/klopator.php";
public static String[] received;
String restaurants[] = new String[40];
String logos_as_strings[] = new String[40];
Bitmap logos[] = new Bitmap[40];
int num_of_elements = 0;
int data_received = 0;
/* user data */
String person_email;
String person_name;
String restaurant;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_after_login);
/* interface main elements */
afterLoginLayout = findViewById(R.id.afterLoginLayout);
restaurantListLayout = findViewById(R.id.restaurantListLayout);
restaurantEditText = findViewById(R.id.restaurantEditText);
findRestaurantButton = findViewById(R.id.findRestaurantButton);
restaurantsRecyclerView = findViewById(R.id.restaurantsRecyclerView);
signOutButton = findViewById(R.id.signOutButton);
loadingAnimation = new AlphaAnimation(1F, 0.8F);
loadingDialog = new LoadingDialog(AfterLoginActivity.this);
/* UPDATING INTERFACE ELEMENTS */
/* execution thread */
final Handler handler = new Handler();
final int delay = 2000; // 1000 milliseconds == 1 second
handler.postDelayed(new Runnable() {
public void run() {
/* check if recycler view is set */
if(recycler_set == 0){
/* if not, check if there is data to fil it with */
if(data_received == 1){
/* convert received strings to images */
for(int i = 0; i < num_of_elements; i++){
logos[i] = stringToBitmap(logos_as_strings[i]);
}
/* fill interface elements */
loadingDialog.dismissDialog();
myAdapter = new MyAdapter(AfterLoginActivity.this, restaurants, logos, num_of_elements);
restaurantsRecyclerView.setAdapter(myAdapter);
restaurantsRecyclerView.setLayoutManager(new LinearLayoutManager(AfterLoginActivity.this));
afterLoginLayout.setVisibility(View.GONE);
restaurantListLayout.setVisibility(View.VISIBLE);
recycler_set = 1;
}
}
handler.postDelayed(this, delay);
}
}, delay);
/* catch restaurant name from user's entry */
findRestaurantButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
restaurant = restaurantEditText.getText().toString();
if(!restaurant.isEmpty()){
view.startAnimation(loadingAnimation);
loadingDialog.startLoadingDialog();
sendRequest();
} else{
Toast.makeText(AfterLoginActivity.this, "Unesite naziv restorana!", Toast.LENGTH_LONG).show();
}
}
});
/* enable signing out */
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
signOutButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.signOutButton:
signOut();
break;
}
}
});
/* obtaining email */
GoogleSignInAccount acct = GoogleSignIn.getLastSignedInAccount(this);
if (acct != null) {
person_email = acct.getEmail();
person_name = acct.getDisplayName();
}
}
#Override
public void onBackPressed() {
afterLoginLayout.setVisibility(View.VISIBLE);
restaurantsRecyclerView.setVisibility(View.GONE);
data_received = 0;
recycler_set = 0;
}
private void signOut() {
mGoogleSignInClient.signOut()
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
Toast.makeText(AfterLoginActivity.this, "Signed out", Toast.LENGTH_LONG).show();
finish();
}
});
}
public void sendRequest(){
StringRequest stringRequest = new StringRequest(Request.Method.POST, UploadUrl, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
try {
JSONObject jsonObject = new JSONObject(response);
String Response = jsonObject.getString("response");
if (Response.equals("Restaurant not found")){
loadingDialog.dismissDialog();
Toast.makeText(getApplicationContext(), "Uneti restoran ne postoji u sistemu! Proverite da li ste dobro napisali naziv", Toast.LENGTH_LONG).show();
} else{
received = Response.split(";");
if (received.length > 0){
data_received = 1;
num_of_elements = received.length / 2;
//Toast.makeText(getApplicationContext(), "num of elements: " + num_of_elements, Toast.LENGTH_LONG).show();
for(int i = 0; i < num_of_elements; i++){
logos_as_strings[i] = received[i*2];
restaurants[i] = received[i*2+1];
//Toast.makeText(getApplicationContext(), "restaurants: " + restaurants, Toast.LENGTH_LONG).show();
}
} else{
loadingDialog.dismissDialog();
Toast.makeText(getApplicationContext(), "Greška u prijemu", Toast.LENGTH_LONG).show();
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(final VolleyError error) {
//volleyError = error;
}
})
{
#Override
protected Map<String, String> getParams() throws AuthFailureError {
//Toast.makeText(getApplicationContext(), "get params", Toast.LENGTH_LONG).show();
Map<String, String> params = new HashMap<>();
params.put("control", "find_restaurant");
params.put("restaurant", restaurant);
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(AfterLoginActivity.this);
requestQueue.add(stringRequest);
}
public static Bitmap stringToBitmap(String encodedString) {
try {
byte[] encodeByte = Base64.decode(encodedString, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
} catch (Exception e) {
e.getMessage();
return null;
}
}
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
String restaurants[];
Bitmap logos[];
Context context;
int num_of_elements;
public MyAdapter(Context ct, String rests[], Bitmap lgs[], int num){
context = ct;
restaurants = rests;
logos = lgs;
num_of_elements = num;
Toast.makeText(context, Integer.toString(restaurants.length), Toast.LENGTH_LONG).show();
for(int i = 0; i < restaurants.length; i++){
Toast.makeText(context, restaurants[i], Toast.LENGTH_SHORT).show();
}
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
View view = layoutInflater.inflate(R.layout.restaurant_item_layout, parent, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyAdapter.MyViewHolder holder, int position) {
holder.restaurantNameTextView.setText(restaurants[position]);
holder.restaurantLogoImageView.setImageBitmap(logos[position]);
holder.restaurantItemLayout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(context, AfterPickingRestaurantActivity.class);
ByteArrayOutputStream bs = new ByteArrayOutputStream();
logos[position].compress(Bitmap.CompressFormat.PNG, 50, bs);
intent.putExtra("byteArray", bs.toByteArray());
intent.putExtra("picked_restaurant_name", restaurants[position]);
context.startActivity(intent);
}
});
}
#Override
public int getItemCount() {
/*
int i = 0;
if (restaurants != null){
while(restaurants[i] != null){
i++;
}
} else{
i = restaurants.length;
}
return i;
*/
return num_of_elements;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView restaurantNameTextView;
ImageView restaurantLogoImageView;
LinearLayout restaurantItemLayout;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
restaurantNameTextView = itemView.findViewById(R.id.restaurantNameTextView);
restaurantLogoImageView = itemView.findViewById(R.id.restaurantLogoImageView);
restaurantItemLayout = itemView.findViewById(R.id.restaurantItemLayout);
}
}
Add this lines after getting new data
myAdapter.notifyDataSetChanged()
Your Adapter Already set with RecyclerView in OnCreate Method.
so when'er you click on findRestaurantButton you just get Resturent data from server but collected data not pass in adapter so thats why to getting blank adapter..
Just put this line in your onResponse after set data in array..
myAdapter.notifyDataSetChanged()
I found out where is the mistake. If you take a look at the section where overriding of back button click is happening, I by mistake set to GONE recyclerView itself:
restaurantsRecyclerView.setVisibility(View.GONE);
instead of LinearLayout in which recyclerView is contained. Desired action was:
restaurantListLayout.setVisibility(View.GONE);
P.S. Everything works without calling notifyDataSetChanged(), I just create a new instance of myAdapater each time when I receive new data. I hope Garbage Collector is doing its job :)
Thanks everyone on help!
I am quite new to the Android Development and I really need your help. My problem is in the MainActivity below. The app essentially displays a list of movies in the main activity and the movie details in another activity. And the problem is that whenever a user comes back from the MovieActivity to the MainActivity, the loader starts loading data again, although the movies are already there. And then it can not stop loading the data. It is really annoying. I want to get rid of this. So when a user comes back to the MainActivity, the loader will know that there is already loaded data and will not load anything again.If it helps, here is my full GitHub repo https://github.com/mateuszwojnarowicz/PopularMovies
I am stuck for about 3 weeks and have tried hundreds of possible solutions. Nothing seems to work. I feel really desperate.
Thank you so much for help,
Matthew
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
private MovieAdapter mAdapter;
private ArrayList<Movie> mMoviesCollection;
private SharedPreferences sharedPreferences;
private Resources resources;
private LoaderManager loaderManager;
private Loader<String> loader;
private RecyclerView.LayoutManager layoutManager;
private String sortBy;
#BindView(R.id.pb)
ProgressBar progressBar;
#BindView(R.id.er)
TextView errorTextView;
#BindView(R.id.rv)
RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mMoviesCollection = new ArrayList<Movie>();
sharedPreferences = getSharedPreferences(Constants.SHARED_PREFS, Activity.MODE_PRIVATE);
resources = getResources();
sortBy = sharedPreferences.getString(Constants.KEY_SORT, null);
setSharedPref();
layoutManager = new GridLayoutManager(this, calculateNoOfColumns(this));
loaderManager = getLoaderManager();
loader = loaderManager.getLoader(Constants.LOADER_MOVIES_ID);
initialize();
makeOperationLoadMovies(sortBy);
}
public static int calculateNoOfColumns(Context context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
float dpWidth = displayMetrics.widthPixels / displayMetrics.density;
int noOfColumns = (int) (dpWidth / 150);
return noOfColumns;
}
//Set first-launch pref and set title according to pref
private void setSharedPref(){
if(!sharedPreferences.contains(Constants.KEY_SORT)) {
saveData(Constants.VALUE_POP);
setTitle(resources.getString(R.string.title_pop));
} else {
if (Objects.equals(sharedPreferences.getString(Constants.KEY_SORT, null), Constants.VALUE_POP)) {
setTitle(resources.getString(R.string.title_pop));
}
if (Objects.equals(sharedPreferences.getString(Constants.KEY_SORT, null), Constants.VALUE_TOP)) {
setTitle(resources.getString(R.string.title_top));
}
}
}
//Set up the RecyclerView
private void initialize(){
recyclerView.setLayoutManager(layoutManager);
recyclerView.setHasFixedSize(true);
mMoviesCollection = new ArrayList<>();
mAdapter = new MovieAdapter(mMoviesCollection, this, this);
recyclerView.setAdapter(mAdapter);
}
private void makeOperationLoadMovies(String SORT_BY){
Bundle bundle = new Bundle();
bundle.putString(Constants.LOADER_MOVIES_EXTRA, SORT_BY);
if(recyclerView.isDirty()){
}
else if(loader==null){
loaderManager.initLoader(Constants.LOADER_MOVIES_ID, bundle, this);
}else{
loaderManager.restartLoader(Constants.LOADER_MOVIES_ID, bundle, this);
}
}
//Update shared pref
private void saveData(String SORT_VALUE){
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(Constants.KEY_SORT, SORT_VALUE);
editor.apply();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id){
case R.id.menu_fav:
startActivity(new Intent(MainActivity.this, FavoritesActivity.class));
break;
case R.id.menu_pop:
saveData(Constants.VALUE_POP);
Toast.makeText(this, resources.getString(R.string.message_popularity),Toast.LENGTH_LONG).show();
break;
case R.id.menu_top:
saveData(Constants.VALUE_TOP);
Toast.makeText(this, resources.getString(R.string.message_rating),Toast.LENGTH_LONG).show();
break;
}
return super.onOptionsItemSelected(item);
}
#Override
protected void onPause() {
super.onPause();
Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();//save
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
}
#Override
protected void onPostResume() {
super.onPostResume();
Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();//save
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
}
#SuppressLint("StaticFieldLeak")
#Override
public Loader<String> onCreateLoader(int id, final Bundle args) {
return new AsyncTaskLoader<String>(this) {
#Override
protected void onStartLoading() {
super.onStartLoading();
forceLoad();
progressBar.setVisibility(View.VISIBLE);
errorTextView.setVisibility(View.INVISIBLE);
}
#Override
public void deliverResult(String data) {
super.deliverResult(data);
}
#Override
public String loadInBackground() {
String jsonString = "";
URL url = NetworkUtils.buildUrl(args.getString(Constants.LOADER_MOVIES_EXTRA));
try {
jsonString += NetworkUtils.getResponseFromHttpUrl(url);
} catch (IOException e) {
e.printStackTrace();
}
if(jsonString.isEmpty()){
} else {
try {
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray jsonArray = jsonObject.getJSONArray(Constants.JSON_KEY_MOVIE_RESULTS);
for (int i = 0; i < jsonArray.length(); i++) {
//Get 1 movie from JSON
String mTitle;
int mId;
String mPosterUrl;
String mPlot;
double mUserRating;
String mReleaseDate;
JSONObject Jmovie = (JSONObject) jsonArray.get(i);
mTitle = Jmovie.getString(Constants.JSON_KEY_MOVIE_TITLE);
mId = Jmovie.getInt(Constants.JSON_KEY_MOVIE_ID);
mPosterUrl = NetworkUtils.getPosterString(Jmovie.getString(Constants.JSON_KEY_MOVIE_POSTER_PATH));
mPlot = Jmovie.getString(Constants.JSON_KEY_MOVIE_OVERVIEW);
mUserRating = Jmovie.getDouble(Constants.JSON_KEY_MOVIE_VOTE_AVERAGE);
mReleaseDate = Jmovie.getString(Constants.JSON_KEY_MOVIE_RELEASE_DATE);
//Get videos
ArrayList<Video> mVideos = new ArrayList<Video>();
URL videosURL = NetworkUtils.buildUrlVideos(String.valueOf(mId));
String videosJSON = NetworkUtils.getResponseFromHttpUrl(videosURL);
JSONObject jsonObjectVideos = new JSONObject(videosJSON);
JSONArray jsonArrayVideos = jsonObjectVideos.getJSONArray(Constants.JSON_KEY_VIDEO_RESULTS);
if(jsonArrayVideos.length()==0){
mVideos = null;
} else {
for(int v = 0; v < jsonArrayVideos.length(); v++){
JSONObject Jvideo = (JSONObject) jsonArrayVideos.get(v);
String mVideoName;
String mVideoUrlString;
mVideoName = Jvideo.getString(Constants.JSON_KEY_VIDEO_NAME);
mVideoUrlString = "https://www.youtube.com/watch?v="+Jvideo.getString(Constants.JSON_KEY_VIDEO_KEY);
Video video = new Video(mVideoName, mVideoUrlString);
mVideos.add(video);
}
}
//GetReviews
ArrayList<Review> mReviews = new ArrayList<Review>();
URL reviewsURL = NetworkUtils.buildUrlReviews(String.valueOf(mId));
String reviewsJSON = NetworkUtils.getResponseFromHttpUrl(reviewsURL);
JSONObject jsonObjectReviews = new JSONObject(reviewsJSON);
JSONArray jsonArrayReviews = jsonObjectReviews.getJSONArray(Constants.JSON_KEY_REVIEW_RESULTS);
if(jsonArrayReviews.length()!=0) {
for(int r = 0; r < jsonArrayReviews.length(); r++){
JSONObject Jreview = (JSONObject) jsonArrayReviews.get(r);
String mReviewName;
String mReviewText;
mReviewName = Jreview.getString(Constants.JSON_KEY_REVIEW_AUTHOR);
mReviewText = Jreview.getString(Constants.JSON_KEY_REVIEW_CONTENT);
Review review = new Review(mReviewName, mReviewText);
mReviews.add(review);
}
}
Movie movie = new Movie(mTitle, mId, mPosterUrl, mPlot, mUserRating, mReleaseDate, mVideos, mReviews);
mMoviesCollection.add(movie);
}
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}
return null;
}
};
}
#Override
public void onLoadFinished(Loader<String> loader, String data) {
progressBar.setVisibility(View.GONE);
mAdapter.notifyDataSetChanged();
}
#Override
public void onLoaderReset(Loader<String> loader) {
}
}
Because you are new to Android there is a lot wrong. So, many people probably won't want to chime in. Regardless, I'm new as well and in the same class as you are right now, so I'll give it a shot.
First, your loader is not returning the correct data type. Your loader should be of Loader<List<Movie>> and it should return a new AsyncTaskLoader<List<Movie>>. The reason you want this is to make use of everything the AsyncTaskLoader has to offer. I'll explain further.
Second, we'll cache the data inside the loader by moving the initial reference from the Activity into the loader.
So move private ArrayList<Movie> mMoviesCollection; as an instance variable of your AsyncTaskLoader. Remove the line mMoviesCollection = new ArrayList<Movie>(); from both your onCreate and initialize methods.
In your AsyncTaskLoader, you need to check if your data exists already in your onStartLoading before forceLoad and implement deliverResult.
So, your onStartLoading() should look like this:
#Override
protected void onStartLoading() {
super.onStartLoading();
if(mMoviesCollection.isEmpty()){
forceLoad();
progressBar.setVisibility(View.VISIBLE);
errorTextView.setVisibility(View.INVISIBLE);
} else {
deliverResult(mMoviesCollection)
}
}
And your deliverResult should look like this:
#Override
public void deliverResult(List<Movie> data) {
mMoviesCollection = data;
super.deliverResult(data);
}
Now you need to implement a setData(List<Movie> movies) method that sets your adapter's data instance variable and calls notifyDataSetChanged() in your Adapter. Like so:
public void setData(List<Movie> movies){
mMovies = movies;
notifyDataSetChanged();
}
Get rid of the List<Movie> from your adapter's constructor. This way you can construct the adapter without any data. The adapter's getItemCount() should return 0 if the data is null and the recyclerView will not try to build the view.
With that done you can then call onLoadFinished like this:
#Override
public void onLoadFinished(Loader<List<Movie>> loader, List<Movie> data) {
progressBar.setVisibility(View.GONE);
mAdapter.setData(data);
}
EDIT: Made a correction to account for the ArrayList instantiating as an Instance variable. You can either not instantiate the mMoviesCollection there and then do so later or just check if its empty with mMoviesCollection.isEmpty() as I changed above in onStartLoading.:
EDIT:
You need to get your libraries straight, you are using android.app in some places and android.support in others.
So in your imports change these:
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Loader;
all to:
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
Now the TMDB.org API has a request limit of 40 requests per 10 seconds.
https://developers.themoviedb.org/3/getting-started/request-rate-limiting
Because of this, your Loader is not even completing everything and is throwing an exception. I would suggest breaking up when you call the videos and reviews into the MovieActivity by creating another AsyncTaskLoader there and calling each when the details screen loads.
You could also technically add a Thread.sleep(300) or less to your AsyncTaskLoader but it makes it seriously slow. In other words, you would have to push the data beyond the 10-second mark to load completely.
Now, with that and the changes we have made, everything does survive config changes such as screen rotation.
If you want the data to survive any further you will have to persist the data somehow. Like saving the json response as a string in onSaveInstanceState or saving the JSon String to the database you created.
I use Volley in the onCreate of my Activity which gets a string on my server, then I convert this string to an arraylist,checkedContactsAsArrayList, and I pass it over to my custom adapter using sharedpreferences, which does stuff with the arraylist in the listview.
But the custom adapter keeps getting the previous arraylist in sharedpreferences, not the one I've just got from the server. The Volley call is too late or something - I can see in logcat the latest values are put after they are got, if you know what I mean.
For example:
VolleyCall 1 putString: 1,2,3
VolleyCall 2 putString: 4,5,6
VolleyCall 3 putString: 7,8,9
Custom Adapter 1 getString: gets values of the last time app was used
Custom Adapter 2 getString: 1,2,3
Custom Adapter 3 getString: 4,5,6
Any idea how to fix this? I could try doing the Volley call in the getView of my custom adapter but I've read on Stackoverflow that's bad practice.
Here are the relvant parts of my code - I've slimmed it down a bit, as there's a lot of stuff in there irrelevant to this issue.
Here's the code of my activity, ViewContact:
public class ViewContact extends AppCompatActivity implements android.widget.CompoundButton.OnCheckedChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(activity_view_contact);
//selectPhoneContacts is an empty array list that will hold our SelectPhoneContact info
selectPhoneContacts = new ArrayList<SelectPhoneContact>();
listView = (ListView) findViewById(R.id.listviewPhoneContacts);
StringRequest stringRequest = new StringRequest(Request.Method.POST, ViewContact_URL,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
//toast the response of ViewContact.php, which has been converted to a
//JSON object by the Php file with JSON encode
Toast.makeText(ViewContact.this, "OnResponse is" + response, Toast.LENGTH_LONG).show();
System.out.println("ViewContact: And the response is " + response);
try {
//checkedContacts is a String
String checkedContacts = responseObject.getString("checkedcontacts");
//convert the checkedContacts string to an arraylist
checkedContactsAsArrayList = new ArrayList<String>(Arrays.asList(checkedcontacts.split(",")));
System.out.println("ViewContact: checkedContactsAsArrayList is " + checkedContactsAsArrayList);
//we want to bring the checkedContactsAsArrayList array list to our SelectPhoneContactAdapter.
// It looks like Shared Preferences
//only works easily with strings so best way to bring the array list in Shared Preferences is with
//Gson.
//Here, we PUT the arraylist into the sharedPreferences
SharedPreferences sharedPreferencescheckedContactsAsArrayList = PreferenceManager.getDefaultSharedPreferences(getApplication());
SharedPreferences.Editor editorcheckedContactsAsArrayList = sharedPreferencescheckedContactsAsArrayList.edit();
Gson gsoncheckedContactsAsArrayList = new Gson();
String jsoncheckedContactsAsArrayList = gsoncheckedContactsAsArrayList.toJson(checkedContactsAsArrayList);
editorcheckedContactsAsArrayList.putString("checkedContactsAsArrayList", jsoncheckedContactsAsArrayList);
editorcheckedContactsAsArrayList.commit();
System.out.println("ViewContact: jsoncheckedContactsAsArrayList is " + jsoncheckedContactsAsArrayList);
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),
"Error: " + e.getMessage(),
Toast.LENGTH_LONG).show();
}
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(ViewContact.this, error.toString(), Toast.LENGTH_LONG).show();
}
}) {
#Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<String, String>();
//we are posting review_id into our ViewContact.php file, which
//we get when a row is clicked in populistolistview
//to get matching details
params.put("review_id", review_id);
return params;
}
};
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(stringRequest);
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//checkBoxforContact.setChecked(true);
}
//******for the phone contacts in the listview
// Load data in background
class LoadContact extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... voids) {
//we want to delete the old selectContacts from the listview when the Activity loads
//because it may need to be updated and we want the user to see the updated listview,
//like if the user adds new names and numbers to their phone contacts.
selectPhoneContacts.clear();
SelectPhoneContact selectContact = new SelectPhoneContact();
selectContact.setName(phoneNameofContact);
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
adapter = new SelectPhoneContactAdapter(selectPhoneContacts, ViewContact.this,0);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
#Override
protected void onResume() {
super.onResume();
// getPrefs();
ViewContact.LoadContact loadContact = new ViewContact.LoadContact();
loadContact.execute();
Toast.makeText(ViewContact.this, "resuming!", Toast.LENGTH_SHORT).show();
}
}
And my custom adapter, SelectPhoneContactAdapter :
public class SelectPhoneContactAdapter extends BaseAdapter {
//define a list made out of SelectPhoneContacts and call it theContactsList
public List<SelectPhoneContact> theContactsList;
//define an array list made out of SelectContacts and call it arraylist
private ArrayList<SelectPhoneContact> arraylist;
Context _c;
ArrayList<String> MatchingContactsAsArrayList;
ArrayList<String> checkedContactsAsArrayList;
ArrayList <String> allNamesofContacts;
String contactToCheck;
//we will run through different logic in this custom adapter based on the activity that is passed to it
private int whichactivity;
String phoneNumberofContact;
String[] phoneNumberofContactStringArray;
String ContactsString;
Intent intent;
public SelectPhoneContactAdapter(final List<SelectPhoneContact> selectPhoneContacts, Context context, int activity) {
theContactsList = selectPhoneContacts;
_c = context;
this.arraylist = new ArrayList<SelectPhoneContact>();
this.arraylist.addAll(theContactsList);
whichactivity = activity;
//we are fetching the array list checkedContactsAsArrayList, created in ViewContact.
//with this we will put a tick in the checkboxes of contacts the review is being shared with
SharedPreferences sharedPreferencescheckedContactsAsArrayList = PreferenceManager.getDefaultSharedPreferences(_c);
Gson gsoncheckedContactsAsArrayList = new Gson();
String jsoncheckedContactsAsArrayList = sharedPreferencescheckedContactsAsArrayList.getString("checkedContactsAsArrayList", "");
Type type2 = new TypeToken<ArrayList<String>>() {
}.getType();
checkedContactsAsArrayList = gsoncheckedContactsAsArrayList.fromJson(jsoncheckedContactsAsArrayList, type2);
System.out.println("SelectPhoneContactAdapter checkedContactsAsArrayList :" + checkedContactsAsArrayList);
}
}
#Override
public int getCount() {
return arraylist.size();
}
#Override
public Object getItem(int i) {
return arraylist.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
static class ViewHolder {
//In each cell in the listview show the items you want to have
//Having a ViewHolder caches our ids, instead of having to call and load each one again and again
TextView title, phone;
CheckBox check;
Button invite;
}
#Override
public View getView(final int i, View convertView, ViewGroup viewGroup) {
//this is the SelectPhoneContact object; consists of textboxes, buttons, checkbox
final SelectPhoneContact data = (SelectPhoneContact) arraylist.get(i);
ViewHolder holder = null;
if (convertView == null) {
//if there is nothing there (if it's null) inflate the view with the layout
LayoutInflater li = (LayoutInflater) _c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = li.inflate(R.layout.phone_inflate_listview, null);
holder = new ViewHolder();
//So, for example, title is cast to the name id, in phone_inflate_listview,
//phone is cast to the id called no etc
holder.title = (TextView) convertView.findViewById(R.id.name);
holder.phone = (TextView) convertView.findViewById(R.id.no);
holder.invite = (Button) convertView.findViewById(R.id.btnInvite);
holder.check = (CheckBox) convertView.findViewById(R.id.checkBoxContact);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//in the listview for contacts, set the name
holder.title.setText(data.getName());
//in the listview for contacts, set the number
holder.phone.setText(data.getPhone());
holder.check.setTag(data);
return convertView;
}
}
Call this: loadContact.execute();
After you call .commit();
ViewContact.LoadContact loadContact = new ViewContact.LoadContact();
loadContact.execute();
I have viewed over 50 pages to find solution for my extremely simple app, but none seems to work for me. Please help.
Problem: I have a refresh button in the menu in MainActivity. When this is pressed, I want to execute my AsyncTask, then update ALL items in my RecyclerView.
Situation.
My app fetches data from API by OpenWeatherMap.org, then displays the data.
I have a MainActivity class (And my recyclerView resides in here.)
For recyclerView, I'm using RecyclerView.Adapter with GridViewManager.
I have a separate AsyncTask class.
So, what I have tried and did not work:
Method 1. Normal way. When refresh button selected, call my AsyncTask. In my PostExecute(), I am calling setter in the MainActivity
public void setWeatherData(String[] weatherData) {this.weatherData = weatherData;}
to assign the result array from doInBackGround method.
Then in MainActivity,
myAsyncTask.execute("43017,us");
recyclerView.recyclerView.getAdapter().notifyDataSetChanged();
But this causes notifyDataSetChanged(); to be called BEFORE member vairable array in MainActivity is updated from onPostExecute().
Method 2. Trying to update UI entirely from onPostExecute method in MyAsyncTask.class.
Well I know onPostExecute, even when it is written in different class, runs on the UI thread. So within the method, I did something like
MainActivity mainActivity = new MainActivity();
RecyclerView recyclerView = mainActivity.findViewById(R.id.recyclerView);
RecyclerView.Adapter adapter = recyclerView.getAdapter();
adapter.notifyDataSetChanged();
In this method, with logging, I've confirmed that onPostExecute successfully updates the member variable in the MainActivity, it's just that notifyDataSetChanged gets called TOO early, specifically before onPostExecute is complete in the background.
I hope to get an answer for this and was clear enough about my situation. I will post my MainActivity, Adapter and AsyncTask codes down below.
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private String weatherData[] = {
"Today - Sunny",
"Tomorrow - Cloudy",
"Tuesday - Rainy",
"Wednesday - Sunny",
"Thursday - Sunny",
"Friday - Sunny",
"Saturday - Cloudy",
"Sunday - Rainy :/"
};
private RecyclerView recyclerView;
private mAdapter adapter;
private static final int SPAN_COUNT = 1;
private MyAsyncTask myAsyncTask = new MyAsyncTask();
private Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.recyclerview_weatherData);
setLayout(getApplicationContext());
adapter = new mAdapter(weatherData);
recyclerView.setAdapter(adapter);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id){
case R.id.action_refresh:
Log.v("Menu", "Refresh button selected.");
//for now, take some random ZIP code
myAsyncTask.execute("43017,us");
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
}
return true;
}
public void setLayout(Context context) {
int scrollPosition = 0;
//make a GridLayoutManager with 2 columns
LinearLayoutManager mLayoutManager = new LinearLayoutManager(context);
//set the mLayoutManager to the one that I just created
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.scrollToPosition(scrollPosition);
recyclerView.setLayoutManager(new GridLayoutManager(context, SPAN_COUNT));
//set the offset decoration definition to my layout
int middle_spacing = 30;
boolean includeEdge = true;
recyclerView.addItemDecoration(new ItemOffsetDecoration(SPAN_COUNT, middle_spacing, includeEdge));
}
public void setWeatherData(String[] weatherData) {
this.weatherData = weatherData;
}
public String[] getWeatherData() { return weatherData; }
}
MyAsyncTask.java:
public class MyAsyncTask extends AsyncTask<String,Void,String[]> {
public final static String OPEN_WEATHER_MAP_API_KEY = "bc607b72747aa672bf2ac9a5f3a5fc84";
String forecastJsonStr = null;
private String format = "json";
private String units = "metric";
private int numDays = 7;
private String data[] =null;
private RecyclerView recyclerView;
private MainActivity mainActivity;
private RecyclerView.Adapter adapter;
#Override
protected String[] doInBackground(String... params) {
if (params.length == 0) {
Log.v("AsyncTask", "No parameter is taken.");
return null;
}
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
try {
final String FORECAST_BASE_URL = "http://api.openweathermap.org/data/2.5/forecast/daily?";
final String QUERY_PARAM = "q";
final String FORMAT_PARAM = "mode";
final String UNITS_PARAM = "units";
final String DAYS_PARAM = "cnt";
final String APPID_PARAM = "APPID";
Uri builtUri = Uri.parse(FORECAST_BASE_URL).buildUpon()
.appendQueryParameter(QUERY_PARAM, params[0])
.appendQueryParameter(FORMAT_PARAM, format)
.appendQueryParameter(UNITS_PARAM, units)
.appendQueryParameter(DAYS_PARAM, Integer.toString(numDays))
.appendQueryParameter(APPID_PARAM, OPEN_WEATHER_MAP_API_KEY)
.build();
URL url = new URL(builtUri.toString());
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
InputStream inputStream = urlConnection.getInputStream();
StringBuffer buffer = new StringBuffer();
if (inputStream == null) {
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line + "\n");
}
if (buffer.length() == 0) {
return null;
}
//put the buffer in String var forecastJsonStr
forecastJsonStr = buffer.toString();
Log.v("AsyncTask", forecastJsonStr.toString());
} catch (IOException e) {
return null;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
Log.e("Async", "Reader is null, something wrong.");
}
}
}
//Then put the string contents into an array
try {
ParseWeatherData parser = new ParseWeatherData();
data = parser.getWeatherDataFromJson(forecastJsonStr, numDays);
return data;
} catch (JSONException e) {
e.printStackTrace();
}
return data;
}
#Override
protected void onPostExecute(final String data[]) {
super.onPostExecute(data);
if (data != null) {
//this log works fine: the fetched data is successfully stored...
for (int i = 0; i<data.length; i++) {
Log.v("onPostExecute", data[i].toString());
}
//how do I pass this data to the main thread?
mainActivity = new MainActivity();
mainActivity.setWeatherData(data);
}
}
}
Finally, mAdapter.java:
public class mAdapter extends RecyclerView.Adapter<mAdapter.ViewHolder> {
private String data[];
public mAdapter(String data[]) {
this.data = data;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View listView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_single_list, parent, false);
return new ViewHolder(listView);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.weather.setText(data[position]);
Log.v("BindView", "Item " + position + " set.");
}
#Override
public int getItemCount() {
if (data == null) {
Log.v("WeatherAdapter", "Oops, getting null in the adapter.");
return 0;
} else {
return data.length;
}
}
public void refreshContents(String data[]) {
this.data = null;
this.data = data;
notifyDataSetChanged();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView weather, day;
//currently ViewHolder is set as the TextView for logging
public ViewHolder(View v) {
super(v);
weather = (TextView) v.findViewById(R.id.test_text);
// Define click listener for the ViewHolder's View.
v.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Element " + getAdapterPosition() + " clicked.");
}
});
}
}
}
Thank you in advance!
First don't create new object of MainActivity in MyAsyncTask. Actually what's happening is AsyncTask runs on a new Thread so when you do
myAsyncTask.execute("43017,us");
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
then myAsyncTask runs in a different thread and the next lines start executing right before .execute without waiting for asynctask to finish and even if it waits for the asynctask to finish, creating a new object to update the data in MainActivity is not gonna work. So what you can do to update your list from onPostExecute method is pass your Activity to myAsyncTask from parameters. Don't initialize your myAsyncTask in the beginning, just remove the = new MyAsyncTask() from private MyAsyncTask myAsyncTask = new MyAsyncTask(); in MainActivity and now replace the lines in MainActivity with this:
Instead of this in your MainActivity
myAsyncTask.execute("43017,us");
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
Write this
myAsyncTask = new MyAsyncTask(this);
myAsyncTask.execute("43017,us");
and to refresh the contents you can make your adapter public so that you could call notifydatasetchanged from myAsyncTask itself but if you want to follow your code written after .execute then you can move it to a new method something like below
public void refreshList(){
recyclerView.getAdapter().notifyDataSetChanged();
adapter.refreshContents(weatherData);
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
}
and now what's left is getting context of your MainActivity in MyAsyncTask and refreshing your list in onPostExecute. So make changes like following:
Create a constructor of your MyAsyncTask
public class MyAsyncTask extends AsyncTask<String,Void,String[]> {
MainActivity mainActivity;
public MyAsyncTask(MainActivity mainActivity){
this.mainActivity = mainActivity;
}
Replace this in MyAsyncTask
mainActivity = new MainActivity();
mainActivity.setWeatherData(data);
With this
mainActivity.setWeatherData(data);
mainActivity.refreshList();
I din't tested it myself but i think this should solve your problem. You can try debugging this and see how it is working. And if you face any problems please comment down below.
Edit
Based on the comment by #Ganesh Patil you can create an interface for this solution as well. For the reference of using interface in AsyncTask you can follow this link:
https://stackoverflow.com/a/28958913/7071039
But to keep it simple i didn't used interface and just passed the context of MainActivity in the MyAsyncTask
Editing based on the comment
Dear PC HUB, firstly thank you so much for your very detailed answer.
All of your explanations made sense and I applied the codes. My app
now doesn't crash and all the data is properly passed / retrieved but
somehow mainActivity.refreshList() and
recyclerView.getAdapter().notifyDataSetChanged() is still not working.
Hence, my recyclerView still doesn't change the contents at all... I
am not sure of what to do. – Rikuto Echigoya
change your refreshList into this
public void refreshList(){
/* Check your Weather Data size in this method to find out weather your data is changing or not */
Log.d("Tag","SIZE OF WEATHER DATA : "+weatherData.length);
/* You already have the adapter object so you don't need to
get it using recyclerview.getAdapter. Just do it directly like this */
adapter.notifyDataSetChanged();
// Not changing this as this will not stop your list from refreshing :P
for (int i = 0; i<weatherData.length; i++) {
Log.v("Refresh button", weatherData[i].toString());
}
}
Also make sure that you've called mainActivity.setWeatherData(data); before mainActivity.refreshList(); in your MyAsyncTask.
If it still doesn't solve your problem then share your updated code so that we could see why your list is not getting updated :)
I've looked at many different variations of code on how to get a list of installed applications and show them in a ListView to the user but none have been successful for me. What I'd like to know is how to do this and how I'd add flags(?) to it so that I could just list curtain applications which have different intent-filters such as LONG_SEARCH_BUTTON and be able to view the package and launcher class of that application.. if this is do able?
I'd like a full java class if someone wouldn't mind sharing their knowledge as trying to piece together bits of code is becoming quite stressful! I've been coding android for a few months so I know most of the basic stuff (I'd like to think...) but not don't anything like this before.
I've written a short application which does exactly that... check it out here.
Here is the sample code from the Android documentation. Please be creative for your requirements.
/** * This class holds the per-item data in our Loader. */ public
static class AppEntry {
public AppEntry(AppListLoader loader, ApplicationInfo info) {
mLoader = loader;
mInfo = info;
mApkFile = new File(info.sourceDir);
}
public ApplicationInfo getApplicationInfo() {
return mInfo;
}
public String getLabel() {
return mLabel;
}
public Drawable getIcon() {
if (mIcon == null) {
if (mApkFile.exists()) {
mIcon = mInfo.loadIcon(mLoader.mPm);
return mIcon;
} else {
mMounted = false;
}
} else if (!mMounted) {
// If the app wasn't mounted but is now mounted, reload
// its icon.
if (mApkFile.exists()) {
mMounted = true;
mIcon = mInfo.loadIcon(mLoader.mPm);
return mIcon;
}
} else {
return mIcon;
}
return mLoader.getContext().getResources().getDrawable(
android.R.drawable.sym_def_app_icon);
}
#Override public String toString() {
return mLabel;
}
void loadLabel(Context context) {
if (mLabel == null || !mMounted) {
if (!mApkFile.exists()) {
mMounted = false;
mLabel = mInfo.packageName;
} else {
mMounted = true;
CharSequence label = mInfo.loadLabel(context.getPackageManager());
mLabel = label != null ? label.toString() : mInfo.packageName;
}
}
}
private final AppListLoader mLoader;
private final ApplicationInfo mInfo;
private final File mApkFile;
private String mLabel;
private Drawable mIcon;
private boolean mMounted; }
/** * Perform alphabetical comparison of application entry objects.
*/ public static final Comparator ALPHA_COMPARATOR = new Comparator() {
private final Collator sCollator = Collator.getInstance();
#Override
public int compare(AppEntry object1, AppEntry object2) {
return sCollator.compare(object1.getLabel(), object2.getLabel());
} };
/** * Helper for determining if the configuration has changed in an
interesting * way so we need to rebuild the app list. */ public
static class InterestingConfigChanges {
final Configuration mLastConfiguration = new Configuration();
int mLastDensity;
boolean applyNewConfig(Resources res) {
int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE
|ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0)
{
mLastDensity = res.getDisplayMetrics().densityDpi;
return true;
}
return false;
} }
/** * Helper class to look for interesting changes to the installed
apps * so that the loader can be updated. */ public static class
PackageIntentReceiver extends BroadcastReceiver {
final AppListLoader mLoader;
public PackageIntentReceiver(AppListLoader loader) {
mLoader = loader;
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mLoader.getContext().registerReceiver(this, filter);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mLoader.getContext().registerReceiver(this, sdFilter);
}
#Override public void onReceive(Context context, Intent intent) {
// Tell the loader about the change.
mLoader.onContentChanged();
} }
/** * A custom Loader that loads all of the installed applications.
*/ public static class AppListLoader extends AsyncTaskLoader> {
final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
final PackageManager mPm;
List<AppEntry> mApps;
PackageIntentReceiver mPackageObserver;
public AppListLoader(Context context) {
super(context);
// Retrieve the package manager for later use; note we don't
// use 'context' directly but instead the save global application
// context returned by getContext().
mPm = getContext().getPackageManager();
}
/**
* This is where the bulk of our work is done. This function is
* called in a background thread and should generate a new set of
* data to be published by the loader.
*/
#Override public List<AppEntry> loadInBackground() {
// Retrieve all known applications.
List<ApplicationInfo> apps = mPm.getInstalledApplications(
PackageManager.GET_UNINSTALLED_PACKAGES |
PackageManager.GET_DISABLED_COMPONENTS);
if (apps == null) {
apps = new ArrayList<ApplicationInfo>();
}
final Context context = getContext();
// Create corresponding array of entries and load their labels.
List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());
for (int i=0; i<apps.size(); i++) {
AppEntry entry = new AppEntry(this, apps.get(i));
entry.loadLabel(context);
entries.add(entry);
}
// Sort the list.
Collections.sort(entries, ALPHA_COMPARATOR);
// Done!
return entries;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
#Override public void deliverResult(List<AppEntry> apps) {
if (isReset()) {
// An async query came in while the loader is stopped. We
// don't need the result.
if (apps != null) {
onReleaseResources(apps);
}
}
List<AppEntry> oldApps = apps;
mApps = apps;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(apps);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldApps != null) {
onReleaseResources(oldApps);
}
}
/**
* Handles a request to start the Loader.
*/
#Override protected void onStartLoading() {
if (mApps != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mApps);
}
// Start watching for changes in the app data.
if (mPackageObserver == null) {
mPackageObserver = new PackageIntentReceiver(this);
}
// Has something interesting in the configuration changed since we
// last built the app list?
boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());
if (takeContentChanged() || mApps == null || configChange) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
#Override protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
#Override public void onCanceled(List<AppEntry> apps) {
super.onCanceled(apps);
// At this point we can release the resources associated with 'apps'
// if needed.
onReleaseResources(apps);
}
/**
* Handles a request to completely reset the Loader.
*/
#Override protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (mApps != null) {
onReleaseResources(mApps);
mApps = null;
}
// Stop monitoring for changes.
if (mPackageObserver != null) {
getContext().unregisterReceiver(mPackageObserver);
mPackageObserver = null;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
protected void onReleaseResources(List<AppEntry> apps) {
// For a simple List<> there is nothing to do. For something
// like a Cursor, we would close it here.
} }
An example implementation of a fragment that uses the above loader to show the currently installed applications in a list is below.
public static class AppListAdapter extends ArrayAdapter {
private final LayoutInflater mInflater;
public AppListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_2);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public void setData(List<AppEntry> data) {
clear();
if (data != null) {
addAll(data);
}
}
/**
* Populate new items in the list.
*/
#Override public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);
} else {
view = convertView;
}
AppEntry item = getItem(position);
((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
((TextView)view.findViewById(R.id.text)).setText(item.getLabel());
return view;
} }
public static class AppListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks> {
// This is the Adapter being used to display the list's data.
AppListAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
#Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No applications");
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new AppListAdapter(getActivity());
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
#Override public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Since this
// is a simple array adapter, we can just have it do the filtering.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
mAdapter.getFilter().filter(mCurFilter);
return true;
}
#Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
#Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("LoaderCustom", "Item clicked: " + id);
}
#Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader with no arguments, so it is simple.
return new AppListLoader(getActivity());
}
#Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {
// Set the new data in the adapter.
mAdapter.setData(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
#Override public void onLoaderReset(Loader<List<AppEntry>> loader) {
// Clear the data in the adapter.
mAdapter.setData(null);
} }