Values of a List don't change programmatically - java

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)

Related

Observing WorkManager Work to get finished Work output

I have enqueued a PeriodicWork in WorkManager and want to get its Worker's output data everytime when it's finished but the following code doesn't seem to work as the Log message doesn't appear in Logcat:
WorkManager.getInstance(getApplicationContext())
.getWorkInfoByIdLiveData(MyWork.getId())
.observe(this, new Observer<WorkInfo>() {
#Override
public void onChanged(#Nullable WorkInfo workInfo) {
Log.d("DEBUG", "onChanged()");
}
});
is this the same as lifeCycleOwner ? I have put this instead because lifeCycleOwner is not recognized here.
Based on this and this.
UPDATE:
I have managed to get the Observer working like this:
WorkManager.getInstance(getApplicationContext())
.getWorkInfosByTagLiveData(MY_WORK_TAG).
observeForever(new Observer<List<WorkInfo>>() {
#Override
public void onChanged(#Nullable List<WorkInfo> workInfos) {
Log.d("DEBUG", "onChanged()");
if (workInfos != null && (!(workInfos.isEmpty()))) {
for (WorkInfo wI: workInfos) {
if (wI.getState() == WorkInfo.State.RUNNING) {
\\ handle workinfo here
}
}
}
}
});
is this the same as lifeCycleOwner ? I have put this instead because lifeCycleOwner is not recognized here.
this mean your class you call your code. Actually, when you call getWorkInfoByIdLiveData it returns to LiveData it means it should be listening in Android Component such as Activity, Fragment ... If your class doesn't implementation LifecycleOwner you can use observeForever instead of observe

Android: MVVM is it possible to display a message (toast/snackbar etc.) from the ViewModel

I want to know what is the best approach to display some sort of message in the view from the ViewModel. My ViewModel is making a POST call and "onResult" I want to pop up a message to the user containing a certain message.
This is my ViewModel:
public class RegisterViewModel extends ViewModel implements Observable {
.
.
.
public void registerUser(PostUserRegDao postUserRegDao) {
repository.executeRegistration(postUserRegDao).enqueue(new Callback<RegistratedUserDTO>() {
#Override
public void onResponse(Call<RegistratedUserDTO> call, Response<RegistratedUserDTO> response) {
RegistratedUserDTO registratedUserDTO = response.body();
/// here I want to set the message and send it to the Activity
if (registratedUserDTO.getRegisterUserResultDTO().getError() != null) {
}
}
});
}
And my Activity:
public class RegisterActivity extends BaseActivity {
#Override
protected int layoutRes() {
return R.layout.activity_register;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
ActivityRegisterBinding binding = DataBindingUtil.setContentView(this, layoutRes());
binding.setViewModel(mRegisterViewModel);
}
What would the best approach be in this case?
We can use a SingleLiveEvent class as a solution. But it is a LiveData that will only send an update once. In my personal experience, using an Event Wrapper class with MutableLiveData is the best solution.
Here is a simple code sample.
Step 1 :
Create an Event class (this is a boilerplate code you can reuse for any android project).
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
Step 2 :
At the top of your View Model class, define a MutableLiveData with wrapper (I used a String here, but you can use your required data type), and a corresponding live data for encapsulation.
private val statusMessage = MutableLiveData<Event<String>>()
val message : LiveData<Event<String>>
get() = statusMessage
Step 3 :
You can update the status message within the functions of the ViewModel like this:
statusMessage.value = Event("User Updated Successfully")
Step 4 :
Write code to observe the live data from the View (activity or fragment)
yourViewModel.message.observe(this, Observer {
it.getContentIfNotHandled()?.let {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
Display Toast/snackbar message in view (Activity/Fragment) from viewmodel using LiveData.
Step:
Add LiveData into your viewmodel
View just observe LiveData and update view related task
For example:
In Viewmodel:
var status = MutableLiveData<Boolean?>()
//In your network successfull response
status.value = true
In your Activity or fragment:
yourViewModelObject.status.observe(this, Observer { status ->
status?.let {
//Reset status value at first to prevent multitriggering
//and to be available to trigger action again
yourViewModelObject.status.value = null
//Display Toast or snackbar
}
})

Android Bug or Java Bug : When execute callback find the wrong instance

Recently I use MVP to develop an Android App.
But I find an Android Bug(maybe I am wrong)
I have two presenter NewsPresenter and MainPresenter. NewsPresenter is created an instance in a Fragment NewsFragment,and MainPresenter is created an instance in an Activity MainActivty.Of course MainActivity include NewsFragment.Both two presenter have two method to implements which to handle the success response and the fail response.
NewsPresenter
public class NewsPresenter extends IPresenter implements IAdapter.OnRecycleItemClickListener{
#Override
protected void onIRequestSuccess(int requestId, IResponse response) {
Log.e("TAG","onIRequestSuccess:"+requestId);
....
}
#Override
protected void onIRequestFail(int requestId, Throwable throwable) {
Log.e("TAG","onIRequestFail:"+requestId);
...
}
}
MainPresenter
public class MainPresenter extends IPresenter{
public MainPresenter(Context context, IMain iMain) {
super(context);
}
#Override
protected void onIRequestSuccess(int requestId, IResponse response) {
//do nothing
}
#Override
protected void onIRequestFail(int requestId, Throwable throwable) {
//do nothing
}
}
Now,in the NewsPresenter I try to make a network request.In the BasePresenter which is its super class I make a log to show the class which try to execute the network request.And the log is that:
01-02 20:09:28.281 17206-17206/com.chengtao.culture E/BasePresenter: class com.chengtao.culture.presenter.NewsPresenter
this mean NewsPresenter execute the request.
But in the IPresente which is NewsPresenter and MainPresenter super class,I try to make a log to show the class which handle the response.And the log is that:
01-02 20:09:38.352 17206-17206/com.chengtao.culture E/TAG: class com.chengtao.culture.presenter.MainPresenter
This mains that the MainPresenter handle the response.
IPresenter
abstract class IPresenter extends BasePresenter{
IPresenter(Context context) {
super(context);
}
#Override
public void onRequestSuccess(int requestId, BaseResponse response) {
Log.e("TAG",this.getClass().toString());
IResponse response1 = (IResponse) response;
Log.e("TAG","onRequestSuccess");
onIRequestSuccess(requestId,response1);
}
#Override
public void onRequestFail(int requestId, Throwable throwable) {
Log.e("TAG",this.getClass().toString());
if (throwable == null || throwable.getMessage() == null){
throwable = new Throwable("请求超时");
onIRequestFail(requestId,throwable);
}else {
onIRequestFail(requestId,throwable);
}
}
protected abstract void onIRequestSuccess(int requestId, IResponse response);
protected abstract void onIRequestFail(int requestId, Throwable throwable);
}
I am so confused why I use NewsPresenter to make the request,but MainPresenter handle the response?It's not scientific,because the log show that the request is NewsPresenter execute,MainPresenter has no relationship with NewsPresenter just both of them extends IPresenter.
But once I delete the instance of MainPresenter in the Activity,the response will be handled by the NewsPresenter.
To see the whole code: https://github.com/ParadiseHell/cultural-creative/tree/master/app/Culture
I make sure my code is all right,So I don't know is Android bug or it's Java bug.If someone knows,please tell me, thanks.
Firstly your Mvp architecture is wrong. List and Adapter are related to UI, so adapter logic must be with fragment not with the presenter.
You should create something other called "Service Class" which will extend the BaseService in which the real API call is sent.
Every API call must have each separate Service Class.We should set the callback method to that serviceclass object so that when the API call gets executed the callback is received for that particular function.
Sample code which explains the above paragraph.
class GetNewsService extends BaseService {
void doApiCall(){
// execute API call . Put the request related code in BaseService.
}
}
class NewsPresenter {
// I am writing psudeo code only.
void getNews(){
GetNewsService newsService=new GetNewsService();
newsService.setCallBack(new RequestClass(){
#Override
onSuccessMethod(Response response){
// send response object to view class through interface and update adapter there
}
#Override
onFailMethod(Error error){}
});
newsService.doApiCall();
}
}
First I have to say,I use MVP right,just because my library has a bug,and I have fixed it.
In the library,there is a class AsyncHttpClient which is singleton pattern,of course it's wrong,because I can init only one response interface in the whole project,but every presenter has a response interface,so it's bad for handling the response.So I delete singleton pattern,use a public constructor,and the problem disappears.
So it's not Android bug or Java bug,just my fault for thinking less.

NullPointerException while use getter

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.

Azure Mobile Services Android Error

I'm doing an android app and trying to integrate social login into the application using Azure Mobile Services.
public class SocialLogin extends Activity implements UserAuthenticationCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
// on create code
}
// All the code
#Override
public void onCompleted(MobileServiceUser user, Exception exception, ServiceFilterResponse response) {
if (exception == null) {
//Take user to the logged in view
cacheUserToken(user);
} else {
Log.e("SocialLogin", "User did not login successfully");
}
}
}
I'm getting two errors because of the onCompleted method.
Error:(176, 5) error: method does not override or implement a method from a supertype
Error:(37, 8) error: SocialLogin is not abstract and does not override abstract method onCompleted(MobileServiceUser,Exception,ServiceFilterResponse) in UserAuthenticationCallback
Edit: Fixed the problem by deleting away the .jar file in my lib.
Per my understanding, 'UserAuthenticationCallback' is not an interface since many of samples are coding like this:
MobileServiceClient mClient = new MobileServiceClient(
"MobileServiceUrl",
"AppKey",
this).withFilter(new ProgressFilter());
mClient.login(MobileServiceAuthenticationProvider.MicrosoftAccount,
new UserAuthenticationCallback() {
#Override
public void onCompleted(MobileServiceUser user,
Exception exception, ServiceFilterResponse response) {
synchronized(mAuthenticationLock)
{
if (exception == null) {
cacheUserToken(mClient.getCurrentUser());
} else {
createAndShowDialog(exception.getMessage(), "Login Error");
}
}
}
});
Since it is not an interface, we cannot implement it as you did. You can either create a class that inherits UserAuthenticationCallback (but the class cannot inherit Activity as we can only inherit one class), or simply create a new instance of UserAuthenticationCallback like the code in the sample.
Also, I'd like to suggest you to check https://azure.microsoft.com/en-us/documentation/articles/mobile-services-android-get-started-users/ and https://azure.microsoft.com/en-us/documentation/articles/mobile-services-android-get-started-data/ for a completed sample of how to add authentication to your Mobile Services Android app.

Categories