I have trouble adding data in ArrayList.
I tried to add data in array list but got nothing
Here's my code
public class fmMain extends Fragment {
private ArrayList<markerList> posList = new ArrayList<markerList>();
public fmMain() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
//Firebase get data
stores.get().addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
for (QueryDocumentSnapshot documentSnapshot: queryDocumentSnapshots){
markerList m = new markerList(documentSnapshot.getId());
posList.add(m);
}
Log.d(TAG,posList.toString()); //got value
}
});
Log.d(TAG,posList.toString()); //got nothing
}
as you can see when I tried to add value in function onSuccess I got value that has been added but when I tried to add outside function I got nothing
And here is my database structure
You cannot simply use the posList list outside the onSuccess() method because it will always be empty due the asynchronous behaviour of this method. This means that by the time you are trying to print posList.toString() outside that method, the data hasn't finished loading yet from the database and that's why is not accessible. A quick solve for this problem would be to use the list only inside the onSuccess() method, as in the following lines of code:
stores.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<GeoPoint> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
list.add(document.getGeoPoint("position"));
}
//Do what you need to do with your list
}
}
});
If you want to use that list outside this method, I recommend you see my anwser from this post where I have explained how you can solve this using a custom callback. You can also take a look at this video for a better understanding.
You are getting this error because you are mixing asynchronous calls with synchronous ones. The onSuccess method (Asynchronous) will be called when you receive the data successfully from Firebase. But the onCreateView method will not wait for that. It will directly go to the second
Log.d(TAG,posList.toString()); //got nothing
and you will get nothing (actually I think you'll get an empty string) because you haven't received the data yet.
I have a util class which helps me to get specific data from Firebase database the class like that
public class FirebaseUtils {
private DatabaseReference root;
public FirebaseUtils(){
root = FirebaseDatabase.getInstance().getReference();
}
public void setUerType(Context context,String userid){
DatabaseReference reference = root.child("teachers").child(userid);
reference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()){
PrefsHelper.getInstance(context).setUserType("teacher");
}else {
PrefsHelper.getInstance(context).setUserType("student");
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
}
what is the best way to remove the listener should I creat a method in utile class like that
public void removeListener(){
child.removeEventListener(listener);
}
and call it in onDetach method in the activity?
Yes, but in your case need to create a member of listener's type and not pass anonymous listener's implementation.
As I see in your code, you are using addListenerForSingleValueEvent(), which means that the listener will read the data precisely once. That means that your onDataChange() method gets triggered with the current value (from the cache if available, otherwise from Firebase servers), and stop listening immediately after that. In this case there is no need to remove the listener.
The only time addListenerForSingleValueEvent needs to be canceled is, if there is no network connection when you attach it and the client doesn't have a local copy of the data, either because there was another active listener or because it has a copy of the data on disk.
So in conclusion, there is no need to create a removeListener() method at all.
I have an List called messages property in my Activity.In the synchronization(),I called getDateMessage(upadated_at) function.In this function value of messages has changed but when program go to synchronization messages list is empty.
private List<message_model> messages = new ArrayList<>();
private void synchronization() {
getDateMessage(upadated_at);
Log.e("MSDF",messages.toString()+" list tostring");
}
private void getDateMessage(String date) {
MessengerActivity.APIInterface apiInterface = app_net.getRetrofitInstance().create(MessengerActivity.APIInterface.class);
retrofit2.Call<List<message_model>> call = apiInterface.getMessageDate(Ptoken, date);
call.enqueue(new Callback<List<message_model>>() {
#Override
public void onResponse(Call<List<message_model>> call, Response<List<message_model>> response) {
if(response.isSuccessful()) {
messages.addAll(response.body());
Log.e("MSDF",response.body().toString()+" responsebody in call");
Log.e("MSDF",messages.toString()+" message in call");
Log.e("MESSAGE", "getDateMessage successful");
}
}
#Override
public void onFailure(Call<List<message_model>> call, Throwable t) {
Log.e("MESSAGE", "getDateMessage" + t.toString());
}
});
}
And This is my logcat.
09-30 14:34:53.714 10763-10763/idea.mahdi.bime E/MSDF: [] list tostring
09-30 14:34:54.104 10763-10763/idea.mahdi.bime E/MSDF: [message_model{id=33, thread_id=2, user_id=15, body='چطوری', created_at='2018-09-29 10:28:26', updated_at='2018-09-29 10:28:26', deleted_at='null'}, message_model{id=30, thread_id=2, user_id=15, body='سلام', created_at='2018-09-29 09:30:40', updated_at='2018-09-29 09:30:40', deleted_at='null'}, message_model{id=7, thread_id=2, user_id=15, body='hi', created_at='2018-09-24 09:55:46', updated_at='2018-09-24 09:55:46', deleted_at='null'}] responsebody in api
09-30 14:34:54.104 10763-10763/idea.mahdi.bime E/MSDF: [message_model{id=33, thread_id=2, user_id=15, body='چطوری', created_at='2018-09-29 10:28:26', updated_at='2018-09-29 10:28:26', deleted_at='null'}, message_model{id=30, thread_id=2, user_id=15, body='سلام', created_at='2018-09-29 09:30:40', updated_at='2018-09-29 09:30:40', deleted_at='null'}, message_model{id=7, thread_id=2, user_id=15, body='hi', created_at='2018-09-24 09:55:46', updated_at='2018-09-24 09:55:46', deleted_at='null'}] message in api
09-30 14:34:54.104 10763-10763/idea.mahdi.bime
E/MESSAGE: getDateMessage successful
The problem is that when you call getDataMessage() it performs an asynchronous call (the retrofit enqueue() method). The server will be called to get the messages in a backgroud thread, while the android application will keep in the main thread.
Therefore, Log.e("MSDF",messages.toString()+" list tostring"); is called before the retrofit call is made, hence, there is no current data available yet. You should make sure that you are doing something with the data after it is completed loaded.
private List<message_model> messages = new ArrayList<>();
private void synchronization() {
getDateMessage(upadated_at);
// Anything you put here will be called before the data (messages) is loaded.
// Do not work with your messages here, they'll be null.
}
private void getDateMessage(String date) {
MessengerActivity.APIInterface apiInterface = app_net.getRetrofitInstance().create(MessengerActivity.APIInterface.class);
retrofit2.Call<List<message_model>> call = apiInterface.getMessageDate(Ptoken, date);
call.enqueue(new Callback<List<message_model>>() {
#Override
public void onResponse(Call<List<message_model>> call, Response<List<message_model>> response) {
if(response.isSuccessful()) {
messages.addAll(response.body());
Log.e("MSDF",response.body().toString()+" responsebody in call");
Log.e("MSDF",messages.toString()+" message in call");
Log.e("MESSAGE", "getDateMessage successful");
// Anything you want to do with the messages should be placed here. When you are sure the data is completed.
Log.e("MSDF",messages.toString()+" list tostring");
}
}
#Override
public void onFailure(Call<List<message_model>> call, Throwable t) {
Log.e("MESSAGE", "getDateMessage" + t.toString());
}
});
}
It's worth checking if (response.body() != null) before doing something with it to avoid NPE.
EDIT
As it was asked in the comments. A good solution (Google recommends it) is to fetch the data using a view model as described in this android dev guide article.
ViewModel approach is good because:
The data persist during configuration changes (for example, if you rotate your device, your list of messages will be still in your app).
It does not cause memory leaks.
You separate view data ownership from UI controller logic.
You can see the other advantages in the article.
1 - Add the view model dependecies in your build.gradle(Module:app) file
dependencies {
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
}
See here the latest version.
2 - Create a ViewModel class
MessageViewModel.java
public class MessagesViewModel extends ViewModel {
private MutableLiveData<List<message_model>> messagesList;
public LiveData<List<message_model>> getMessages() {
if (messagesList == null) {
messagesList = new MutableLiveData<List<message_model>>();
loadMessages();
}
return messagesList;
}
private void loadMessages() {
MessengerActivity.APIInterface apiInterface = app_net.getRetrofitInstance().create(MessengerActivity.APIInterface.class);
retrofit2.Call<List<message_model>> call = apiInterface.getMessageDate(Ptoken, date);
call.enqueue(new Callback<List<message_model>>() {
#Override
public void onResponse(Call<List<message_model>> call, Response<List<message_model>> response) {
if(response.isSuccessful()) {
if (response.body() != null) {
messagesList.setValue(response.body());
}
}
}
#Override
public void onFailure(Call<List<message_model>> call, Throwable t) {
// Handle failure
}
});
}
}
3 - Get the messages in your activity
public class MainActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MessagesViewModel model = ViewModelProviders.of(this).get(MessagesViewModel.class);
model.getMessages().observe(this, messagesList -> {
// Do whatever you want with the list of messages.
});
}
}
Look how clean your activity is now.
Then you can implement a SwipeRefreshLayout if you want to allow your users to refresh the data.
If it is not enough, you can check this ReposViewModel
Finally, if calling retrofit is the main core of your app that is going to be released to the public, you should introduce MVVM approach using Dagger 2 and RxJava, as described in this article. (This is advanced)
I'm migrating my app to the rxJava2 and would like to clarify some things.
In my BasePresenter class I do following:
#Override
public void attachView(T mvpView) {
this.mvpView = mvpView;
compositeDisposable = new CompositeDisposable();
}
#Override
public void detachView() {
compositeDisposable.dispose();
mvpView = null;
}
So if I call compositeDisposable.dispose(); when I detach the view then onNext(), onError() or onComplete() are not going to be called and there is no reason to check isViewAttached() in onNext()?
Is this the right way of using CompositeDisposable in the presenter?
Yes, that is correct. CompositeDisposable is essentially what CompositeSubscription was in the previous version of rxJava.
Hello I have a problem with getter in my custom class, I don't know why it returns null every time. I'm setting value after response from server when it is without any errors. While I'm debugging, I see that response from server is OK and new instance of my object is created but when I try to get it in my activity there is a null. Here is couple lines of code where is a problem (in my opinion).
method from my custom class:
public void responseFromServer(){
showProgressDialog();
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build();
TitleInterface titleInterface = retrofit.create(TitleInterface.class);
Call<MovieResponse> call = titleInterface.getMovie(API_KEY,movie);
call.enqueue(new Callback<MovieResponse>() {
#Override
public void onResponse(Call<MovieResponse> call, Response<MovieResponse> response) {
List<Movie> movieList = response.body().getMovieList();
ItemAdapter itemAdapter = new ItemAdapter(context.getApplicationContext(),generateData(movieList));
setItemAdapter(itemAdapter);
}
#Override
public void onFailure(Call<MovieResponse> call, Throwable t) {
Toast.makeText(context, "ERROR", Toast.LENGTH_SHORT).show();
progressDialog.dismiss();
}
});
}
and here is my Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movie_list);
movieListView = (ListView) findViewById(R.id.movieListView);
String movie = getIntent().getStringExtra(TAG);
presenter = new Presenter(this,movie);
presenter.responseFromServer();
item=presenter.getItemAdapter();
movieListView.setAdapter(presenter.getItemAdapter());
presenter.getItemAdapter().notifyDataSetChanged();
presenter.getProgressDialog().dismiss();
}
Thanks for any help.
It seems like your request haven't finished when the view is created. If you want to make your call synchronous use this:
Call<MovieResponse> call = titleInterface.getMovie(API_KEY,movie);
Response<MovieResponse> responseBody = call.execute();
List<Movie> movieList = response.body().getMovieList();
You are using asynchronous calls in your app, so try to replace all necessary methods for updating UI from onCreate() to onResponse(). It should help. If you want to use Retrofit synchronous calls, the best practice for that is Loaders.