items = new ArrayList<String>();
itemsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items);
listView.setAdapter(itemsAdapter);
registerForContextMenu(listView);
fstore.child(/*MyFolder1*/).child(/*MyFolder2*/).listAll().addOnSuccessListener(new OnSuccessListener<ListResult>() {
#Override
public void onSuccess(ListResult listResult) {
for(StorageReference sref : listResult.getItems())
{
items.add(sref.getName()); //<- NOT working
System.out.println("IIIIII: "+sref.getName()); //sref.getName() returns proper value here
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(tabtwoactivity.this, "Fetching failed", Toast.LENGTH_LONG).show();
}
});
itemsAdapter.notifyDataSetChanged();
Why doesn't sref.getName() get added to the list? All items.add() statements before and after work properly.
Ignore this part - I need to add some more info to be able to publish the question. I hope this much is enough.
The Firebase Storage API listAll() is asynchronous and returns immediately before the results are available. Your callback is invoked some time later, after the results are available. While listAll() is executing, your code does on to immediately render the empty list, and notifyDataSetChanged ends up doing nothing. Your code needs wait for the results to complete before trying to render any views.
Try instead calling itemsAdapter.notifyDataSetChanged() from within the onSuccess callback to force the adapter to render the new results.
fstore.child(/*MyFolder1*/).child(/*MyFolder2*/).listAll().addOnSuccessListener(new OnSuccessListener<ListResult>() {
#Override
public void onSuccess(ListResult listResult) {
for(StorageReference sref : listResult.getItems())
{
items.add(sref.getName()); //<- NOT working
System.out.println("IIIIII: "+sref.getName()); //sref.getName() returns proper value here
}
itemsAdapter.notifyDataSetChanged();
}
You will probably also want to decide what you want to display before listAll() is complete, as it might not be as fast as you want.
Related
I'm having a trouble with firebase queries in Studio.
I'm trying to abstract a simple query to get a list of object stored in a collection (in my case "users")
I want to create a function stored in a Class that can be called by every fragment into the project.
But I don' t find any method to do that, is to repeate the same instruction the only way to do that?
Here is an example
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if(task.getResult() != null) {
for (QueryDocumentSnapshot document : task.getResult()) {
usersList.add(document.toObject(User.class));
} else {
Log.w(LOGIN, "Error getting documents.", task.getException());
}
});
I write these lines of code every time I need them, but I want to create a method that return a List as in this example:
public static List<User> getUsers(FirebaseFirestore db) {
List<User> usersList = new ArrayList<>();
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if(task.getResult() != null) {
for (QueryDocumentSnapshot document : task.getResult()) {
usersList.add(document.toObject(User.class));
} else {
Log.w(LOGIN, "Error getting documents.", task.getException());
}
});
} else {
//error
}
return usersList;
}
Data is loaded from Firestore (and most modern cloud APIs) asynchronously, because it may take some time. Instead of blocking the app during that time, the main code continues to execute. Then when the data is available, your addOnCompleteListener callback is executed with that data.
The easiest way to see this is by adding some well-placed logging to your code:
public static List<User> getUsers(FirebaseFirestore db) {
Log.i(LOGIN, "Starting getUsers");
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
Log.i(LOGIN, "Got data");
})
Log.i(LOGIN, "Returning from getUsers");
}
When you run this code, you get the following output:
Starting getUsers
Returning from getUsers
Got data
This is probably not the order you expected, but it completely explains why the code that calls getUsers never sees the data: by the time your return usersList runs, the data hasn't loaded yet and usersList.add(document.toObject(User.class)) has never been called.
The solution is always the same: any code that needs the data from the database, must either be inside the completion callback, be called from there, or be synchronized by some other means.
A simple example is to create a custom callback function:
public interface GetUsersCallback {
void onCallback(List<User> users);
}
You then pass that to getUsers, which can then call it once it's gotten and processed the results from the database:
public static void getUsers(FirebaseFirestore db, GetUsersCallback callback) {
// 👆
List<User> usersList = new ArrayList<>();
db.collection("users") //get all the users
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
if(task.getResult() != null) {
for (QueryDocumentSnapshot document : task.getResult()) {
usersList.add(document.toObject(User.class));
} else {
Log.w(LOGIN, "Error getting documents.", task.getException());
}
});
callback(usersList); // 👈
}
}
And you can then use it like this:
getUsers(new GetUsersCallback() {
#Override
public void onCallback(List<User> users) {
Log.i(LOGIN, "Found "+users.size()+" users");
}
});
Asynchronous loading of data is incredibly common when dealing with cloud APIs, but it's also quite confusing when you first encounter it. I recommend reading some of these answers to learn more about it:
How to check a certain data already exists in firestore or not
How to return a DocumentSnapShot as a result of a method?
Why does my function that calls an API return an empty or null value?
Firebase Firestore get data from collection
I'm trying to implement the connecty cube API in my app when using the following code the dialogs ArrayList is empty. How can I retrieve all the chat dialogs?
This method is giving me an empty list:
#Override
public void onSuccess(ArrayList<ConnectycubeChatDialog> dialogs, Bundle params) {
int totalEntries = params.getInt(Consts.TOTAL_ENTRIES);
}
complete method code:
RequestGetBuilder requestBuilder = new RequestGetBuilder();
requestBuilder.setLimit(50);
requestBuilder.setSkip(100);
//requestBuilder.sortAsc(Consts.DIALOG_LAST_MESSAGE_DATE_SENT_FIELD_NAME);
ConnectycubeRestChatService.getChatDialogs((ConnectycubeDialogType) null, requestBuilder).performAsync(new EntityCallback<ArrayList<ConnectycubeChatDialog>>() {
#Override
public void onSuccess(ArrayList<ConnectycubeChatDialog> dialogs, Bundle params) {
int totalEntries = params.getInt(Consts.TOTAL_ENTRIES);
}
#Override
public void onError(ResponseException exception) {
}
});
Code
requestBuilder.setSkip(100);
means you want skip 100 items and than select 50. Are you sure you have more than 100 dialogs?
As example you can use theirs sample app.
Here code to load dialogs.
i tried to return list from the url that i get with retrofit. it works and i get the data but it wont return.
this is my code
public List<MovieResponse> loadCourses() {
ArrayList<MovieResponse> list = new ArrayList<>();
ApiServices apiService =
NetworkClient.getRetrofitClient().create(ApiServices.class);
Call<MovieResult> call = apiService.getMovies();
call.enqueue(new Callback<MovieResult>() {
#Override
public void onResponse(Call<MovieResult> call, Response<MovieResult> response) {
if (response.body() != null) {
ArrayList<MovieResponse> movies = new ArrayList<>();
movies = response.body().getResults();
Log.d("",""+movies);
list.addAll(movies);
Log.d("",""+list);
}
}
#Override
public void onFailure(Call<MovieResult> call, Throwable t) {
// Log error here since request failed
Log.e("error", t.toString());
}
});
return list;
}
when i print list inside onResponse it works and there are the data. but when i return it or trying to print list outside onResponse for example below ArrayList<MovieResponse> list = new ArrayList<>(); it not show the data.
please help what is actually wrong with it. i really appreciate it.
The simplest way is to define your movies list directly inside activity or fragment(in other words, a field member of the class).
It's not a good idea to return data from an asynchronous method.
Change the return type of the loadCourses method to void and instantiate the filed movies inside onResponse().
public class SomeActivity extends AppCompatActivity {
private ArrayList<MovieResponse> movies = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_some);
}
public void loadCourses() {
ApiServices apiService =
NetworkClient.getRetrofitClient().create(ApiServices.class);
Call<MovieResult> call = apiService.getMovies();
call.enqueue(new Callback<MovieResult>() {
#Override
public void onResponse(Call<MovieResult> call, Response<MovieResult> response) {
if (response.body() != null) {
movies = response.body().getResults();
...
}
}
#Override
public void onFailure(Call<MovieResult> call, Throwable t) {
...
}
});
}
}
It is because you are making asynchronous call which is being handled by a separate thread. So after call.enqueue(), the main thread directly jumps to return statement without waiting for API response, that's why you are getting empty list.
Assuming your API takes 1 sec to respond,
just for an experiment, you can try adding a sleep() for 3 sec right before your return statement, it should return all the movies.
If you must return from the method then go for retrofit synchronous call.
To make a sync call create a new thread in main thread and make call from there, it is not allowed to make network call from main thread because it blocks the thread.
I am fetching data from api and showing them into recyclerview.
I am using InfiniteScroll to load more data on scroll.
It works fine if I scroll smoothly but if I scroll fast I get NullPointerException as data is not being fetched on time even after I am checking if model data is not null in Adapter class.
How to avoid this situation?
Please find the code below:
#NonNull
private InfiniteScrollListener createInfiniteScrollListener() {
return new InfiniteScrollListener(10, linearLayoutManager) {
#Override public void onScrolledToEnd(final int firstVisibleItemPosition) {
offset += 10;
final List<Purchase2> itemsLocal = loadMore(offset);
refreshView(recyclerView, new PurchaseMainTestAdapter(itemsLocal, R.layout.list_orders_layout, getApplicationContext(), emptyView), firstVisibleItemPosition);
}
};
}
private List<Purchase2> loadMore(int index){
progressBar.setVisibility(View.VISIBLE);
//add loading progress view
purchases.add(new Purchase2());
adapter.notifyItemInserted(purchases.size()-1);
OneViewApi apiService =
ApiClient.getClient().create(OneViewApi.class);
Call<Orders> call;
call = apiService.getPurchaseData(auth_token,index,10);
Log.d("Called url is:", call.request().url().toString());
call.enqueue(new Callback<Orders>() {
#Override
public void onResponse(Call<Orders> call, Response<Orders> response) {
if(response.isSuccessful()){
//remove loading view
purchases.remove(purchases.size()-1);
List<Purchase2> result = response.body().getPurchases();
if(result.size()>0){
//add loaded data
purchases.addAll(result);
}else{//result size 0 means there is no more data available at server
adapter.setMoreDataAvailable(false);
Toast.makeText(getApplicationContext(),"No More Data Available",Toast.LENGTH_LONG).show();
}
progressBar.setVisibility(View.GONE);
//should call the custom method adapter.notifyDataChanged here to get the correct loading status
}else{
Log.e("Item list"," Load More Response Error "+String.valueOf(response.code()));
}
}
#Override
public void onFailure(Call<Orders> call, Throwable t) {
Log.e("Item list"," Load More Response Error "+t.getMessage());
}
});
return purchases;
}
at first use try{} catch{} then trace your code via break point to fine where exception happened
but i guess exception occur in here :purchases.remove(purchases.size()-1); that your list is null and you are trying to remove an item.(in first time) or about adding and removing items.
but for detect showing load more or not you can add null to purchases list then handle it in adapter - it's too better
Got the fix for this null pointer Exception:
1.) Added adapter code inside
try{} catch{} block
2.) Has set the flag so that on scroll it could not call the service again until last one is executed
if(!isLoading) {
isLoading = true;
final List<Purchase2> itemsLocal = loadMorePurchaseData(offset, null, null, null);
refreshView(recyclerView, new PurchaseMainAdapter(itemsLocal, R.layout.list_orders_layout, getApplicationContext(), emptyView), firstVisibleItemPosition);}
And in network call set the appropriate flag:
public void onResponse(Call<Orders> call, Response<Orders> response) {
if(response.isSuccessful()){
List<Purchase2> result = response.body().getPurchases();
if(result.size()>0){
//add loaded data
purchases.addAll(result);
}else{//result size 0 means there is no more data available at server
purchase_adapter.setMoreDataAvailable(false);
//telling adapter to stop calling load more as no more server data available
Toast.makeText(getApplicationContext(),"No More Data Available",Toast.LENGTH_LONG).show();
}
progressBar.setVisibility(View.GONE);
isLoading = false;
//should call the custom method adapter.notifyDataChanged here to get the correct loading status
}else{
Log.e("Item list"," Load More Response Error "+String.valueOf(response.code()));
}
Thanks everyone for giving the hint.
I'm using a bit of code to help me with pulling data from the web called WebRequest (https://github.com/delight-im/Android-WebRequest). It provides a callback for retrieving asynchronous data. However I can't update my ArrayAdapter because I get an error "Non-Static method 'notifyDataSetChanged()' cannot be referenced from a static context"
Now, I've seen a number of similar questions here. Some suggest putting the notifyDataSetChanged command in the Adapter class itself. Some suggest using a Handler and some suggest using a Loader. However I have had no luck actually implementing these solutions. Here's my code:
new WebRequest().get().to(stringSQLUrl).executeAsync(new WebRequest.Callback() {
public void onSuccess(int responseCode, String responseText) {
try {
DataHistory = CsvUtil.fromCsvTable(responseText);
DataHistory.remove(0); //Removes header row
} catch (Exception e) {
Log.e("Main pullWebData","Error converting from CsvTable: " + e.getMessage());
}
DataAdapter.notifyDataSetChanged(); // <-- ERROR HERE
runOnUiThread(new Runnable() {
#Override
public void run() {
DataAdapter.notifyDataSetChanged(); // <-- ALSO ERROR HERE
}
});
}
public void onError() {
Log.e("Main pullWebData","Error pulling data from web");
}
});
I also defined this Handler in my activity thinking I could call it and it would update the ArrayAdapter, but I got the same error here:
private Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
WeightAdapter.notifyDataSetChanged();
}
};
Lastly I created a method inside the Adapter definition to notify itself, but calling that gave me the same static/non-staic error:
public void updateMe() {
this.notifyDataSetChanged();
}
Long story short - there are a ton of questions seemingly about this same topic and lots of advice, but I have been unsuccessful in implementation. Can anyone show me exactly how I'd implement this?
Thank you!
One other thing: I was considering switching from Web data to an Azure SQL DB, but that would also use a callback and I presume have the same issue?
You can only call static methods using ClassName.methodName(); However, notifyDataSetChanged() is not a static method. i.e. notifyDataSetChanged() works depending on the instance of your adapter.
To make sure that this works, you should use notifyDataSetChanged() on the object of the custom adapter.
If you have something like :
DataAdapter customAdapter = new DataAdapter(//params);
listView.setAdapter(customAdapter);
You should call :
customAdapter.notifyDataSetChanged();