Fill an ArrayList with object from response.body - java

I'm using retrofit to get list of data from api. I want to save the data which I get in body.response to arrayList. I can do that and I get the items from new arrayList, but it seems that the new list are not saved. When I try to use it outside of method onResponse, I get an error
Unable to start activity... IndexOutOfBoundsException: Index 0, Size 0
The interface
public interface ServerApi {
#Headers("Accept: application/json")
#GET("cities")
Call<ArrayList<City>> getCities();
}
The activity
public class SomeActivity extends AppCompatActivity {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://url")
.addConverterFactory(GsonConverterFactory.create())
.build();
ServerApi serverApi = retrofit.create(ServerApi.class);
ArrayList<City> cityList = new ArrayList<City>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_parkings);
final TextView tv1 = findViewById(R.id.tv1);
final Call<ArrayList<City>> cities = serverApi.getCities();
cities.enqueue(new Callback<ArrayList<City>>() {
#Override
public void onResponse(Call<ArrayList<City>> call, Response<ArrayList<City>> response) {
for(int i=0; i<response.body().size(); i++){
City ct = new City(response.body().get(i).getId(), response.body().get(i).getName());
cityList.add(ct);
tv1.setText(cityList.get(i).getName());
}
}
#Override
public void onFailure(Call<ArrayList<City>> call, Throwable t) {
tv1.setText("failure " + t);
}
});
TextView c1 = findViewById(R.id.c1);
c1.setText(cityList.get(0).getName());
}
}
The string tv1.setText(cityList.get(i).getName()); inside the method onResponse works well. But when I write it out of the method, the aplication fall

As it can be seen you run your code in a background thread and a few lines below you try to update your textview on the UI thread.
The background operation will take some time, therefore, by the time you try to update the TextView the cityList is empty.
Consider reading a bit more here: Consuming apis with retrofit

Related

LiveData with added new item via push notification not update the list

I tried to add new message item who arrived from push notification to list.
I tried to achieve this by live data. I used databinding in recyclerview and in main activity.
The func onChanged is not called when item is added to live data list in MsgViewModel class.
what I doing wrong?
public class MyFirebaseMessagingService extends FirebaseMessagingService {
......
private void showNotification(Map<String, String> data) {
id = data.get("id");
phone = data.get("phone");
locations = data.get("locations");
textMessage = data.get("textMessage");
MsgViewModel viewModel = new MsgViewModel(getApplication());
viewModel.addMessage(new Message(id, phone, locations, textMessage));
}
public class MsgViewModel extends AndroidViewModel {
private MutableLiveData<ArrayList<Message>> messageArrayList;
public MsgViewModel(#NonNull Application application) {
super(application);
messageArrayList = new MutableLiveData<>();
}
public void addMessage(Message message){
List<Message> messages = messageArrayList.getValue();
ArrayList<Message> cloneMessageList;
if(messages == null){
cloneMessageList = new ArrayList<>();
}else {
cloneMessageList = new ArrayList<>(messages.size());
for (int i = 0; i < messages.size(); i++){
cloneMessageList.add(new Message(messages.get(i)));
}
}
cloneMessageList.add(message);
messageArrayList.postValue(cloneMessageList);
}
public MutableLiveData<ArrayList<Message>> getMessageList(){
return messageArrayList;
}
}
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MsgViewModel msgViewModel;
private MsgListAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.contentMainId.recyclerview.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
binding.contentMainId.recyclerview.setLayoutManager(layoutManager);
msgViewModel = new ViewModelProvider(this).get(MsgViewModel.class);
msgViewModel.getMessageList().observe(this, new Observer<ArrayList<Message>>() {
#Override
public void onChanged(ArrayList<Message> list) {
mAdapter = new MsgListAdapter(getApplication(), list);
binding.contentMainId.recyclerview.setAdapter(mAdapter);
// mAdapter.notifyDataSetChanged();
}
});
}
Any help why it is not update the adapter will be appreciated
==========Update=======
onChanged() method not called when have new item who added to the list
The problem here is how you instantiate your viewModels. If I understand correctly, you want them to be the same instance in both the activity and the messaging service.
One is MsgViewModel viewModel = new MsgViewModel(getApplication());
The other one is msgViewModel = new ViewModelProvider(this).get(MsgViewModel.class);
On the second one this stands for the current instance of the activity. And its context is different from the one you get from getApplication().
As far as I know, when you call 'postValue()' or something 'setValue()' method, you should give new object of something like oldMutableList.toList().
In other words, after a list is entered as a parameter in 'postvalue()'method of livedata, even if a new value is added to the list, livedata is not recognized. In order for the observer to recognize, the newly created list object must be entered as a parameter when calling postvalue again.
_liveData.postValue(list.toList()) // "list.toList()" <- this code generate new List object which has another hashcode.
or
_liveData.postValue(list.sortedBy(it.somethingField))
sorry about this kotlin code, not java.
// this original in your code
messageArrayList.postValue(cloneMessageList);
// change to these codes
messageArrayList.postValue(new ArrayList(cloneMessageList));
or
messageArrayList.postValue(cloneMessageList.toList());

Losing data when sending between two classes

My app doesn't display anything when passing data from one class to another. I located through with the debugger that my ArrayList doesn't get the right value from the class.
I'm sending data with the following function:
public class Adaugare extends AppCompatActivity {
private ListView myListView;
private NumeAdapter numeAdapter;
String inume;
int ivarsta;
Intent intent = new Intent();
private ArrayList persoanaArrayList = new ArrayList<>();
public ArrayList getPersoanaArrayList() {
return persoanaArrayList;
}
public int getPersoanaArrayListsize() {
return persoanaArrayList.size();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_adaugare);
myListView = (ListView) findViewById(R.id.persoana_list);
Button btn_fin = (Button) findViewById(R.id.btn_fin);
btn_fin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
EditText nume_edit_text = (EditText) findViewById(R.id.ins_nume);
EditText varsta_edit_text = (EditText) findViewById(R.id.ins_var);
ivarsta = Integer.parseInt(varsta_edit_text.getText().toString());
inume = nume_edit_text.getText().toString();
persoanaArrayList.add(new Persoana(inume, ivarsta));
}
});
}
}
And recieving it with:
public class Afisare extends AppCompatActivity {
ListView myListView;
NumeAdapter numeAdapter;
Adaugare ad = new Adaugare();
int cate;
int i;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_afisare);
myListView = (ListView) findViewById(R.id.persoana_list);
ArrayList<Persoana> persoanaArrayList = new ArrayList<Persoana>(ad.getPersoanaArrayList());
numeAdapter = new NumeAdapter(this, persoanaArrayList);
myListView.setAdapter(numeAdapter);
}
The class Persoana is:
public class Persoana {
private String nume;
private int varsta;
Persoana(String inume, int ivar) {
this.nume = inume;
this.varsta = ivar;
}
public String getNume() {
return nume;
}
public int getVarsta() {
return varsta;
}
public void setNume(String nume) {
this.nume = nume;
}
public void setVarsta(int varsta) {
this.varsta = varsta;
}
}
Persoana is the main class, everything is saved in it. ad is an object of Adaugare, Adaugare being the class from which I've taken the code for getPersoanaArrayList. At debugging some values appeared at ad, namely Adaugare #4556, and persoanaArrayList remains null.
I need the persoanaArrayList so that i can initialize my Adapter and listView. Everything else in the code seems fine from step by step testing with debugger.
Your problem is with the following line in the Afisare class:
Adaugare ad = new Adaugare();
You can't simply new one activity from another activity and expect to access a shared list between them. To share instance data between java objects you need a reference to the other object. Creating a new instance will create a new empty list. That's why you are "losing" data. A quick fix would be to make the list static so it can be accessed from any instance.
But since you're dealing with Android, the right way to share data between activities is by using intent extras. The first activity starts the second activity via an intent. The first activity places the desired data in the intent as extras. The second activity uses getIntent() and the various methods on Intent to access the extras.
One last tip, in Android, you never use the new operator with Activities. Activities are created by the system to service an intent. If you find yourself using the new operator, that's a sign that you're doing something wrong.

Search FlickR Photos by Tag

I am trying to query FlickR photos and receive a JSON response. I am using Retrofit to make the call to the FlickR API. In my code, the user enters text, which is captured via an EditText. I want to query based on this term. I am receiving the following error from Flick: "Parameterless searches have been disabled. Please use flickr.photos.getRecent instead."
public class MainActivity extends AppCompatActivity {
private EditText mSearchTerm;
private Button mRequestButton;
private String mQuery;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSearchTerm = (EditText) findViewById(R.id.ediText_search_term);
mRequestButton = (Button) findViewById(R.id.request_button);
mRequestButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mQuery = mSearchTerm.getText().toString();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.flickr.com/services/rest/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiInterface apiInterface = retrofit.create(ApiInterface.class);
Call<List<Photo>> call = apiInterface.getPhotos(mQuery);
call.enqueue(new Callback<List<Photo>>() {
#Override
public void onResponse(Call<List<Photo>> call, Response<List<Photo>> response) {
}
#Override
public void onFailure(Call<List<Photo>> call, Throwable t) {
}
});
}
});
}
//Synchronous vs. Asynchronous
public interface ApiInterface {
#GET("?&method=flickr.photos.search&api_key=1c448390199c03a6f2d436c40defd90e&format=json") //
Call<List<Photo>> getPhotos(#Query("q") String photoSearchTerm);
}
}
Based off their API docs you're looking to pass text as the #Query param instead of q. So like:
Call<List<Photo>> getPhotos(#Query("text") String photoSearchTerm);
Other things:
• Might want to hide your API Key from your post.
• Might want to wrap the code in your onClick() method with if(!TextUtils.isEmpty(mQuery)) to prevent the issue from happening again. (Could also add a TextChangeWatcher to your EditText and enable/disable the search button based on the length of the string in the EditText

Android Retrofit 1.9 return ListString upon success

I searched similar questions like this but sadly I found them really confusing and also I'm still new on using Android and Retrofit.
I have a contact list JSON here
http://api.androidhive.info/contacts/
And already working List but then I wanted to handle the Retrofit process to another class so I can just call it whenever I want. I have the MainActivity calling for the UI and the RetrofitHandler which handles the success and failure method.
Here is my Main Activity
public class MainActivity extends AppCompatActivity{
//note i just simplifiend my code a little
private List<Contacts> contacts;
public String[] itemer;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RetrofitHandler retrofitHandler = new RetrofitHandler();
itemer = retrofitHandler.getContacts(this);
if (itemer != null) {
Toast.makeText(MainActivity.this,itemer[0],Toast.LENGTH_SHORT).show();
}
}
And here is my HandlerClass
ublic class RetrofitHandler {
public String[] item;
public static final String ROOT_URL = "http://api.androidhive.info";
public List<Contacts> contacts;
public String[] getContacts(final Context context) {
final ProgressDialog loading = ProgressDialog.show(context, "Fetching Data", "Please wait...", false, false);
RestAdapter adapter = new RestAdapter.Builder().setEndpoint(ROOT_URL).build();
ContactsAPI api = adapter.create(ContactsAPI.class);
api.getContacts(new Callback<Contacts>() {
#Override
public void success(Contacts contacts, Response response) {
loading.dismiss();
MainActivity update = new MainActivity();
List<Contact> contactList = contacts.getContacts();
item = new String[contactList.size()];
for (int i = 0; i < contactList.size(); i++) {
item[i] = contactList.get(i).getName();
}
}
#Override
public void failure(RetrofitError error) {
Toast.makeText(context, "Error Occured", Toast.LENGTH_LONG).show();
}
});
return item;
}
My problem is it runs smoothly with no error on LogCat. Unfortunately the Toast on the mainactivity won't appear.
Your issue is because of asynchronous nature of the request/response. Success or Failure callbacks fires upon response, which is happen in asynchronous manner. So basically your method returns before that happens, it means your item is null. You have to use synchronous mechanism or you have to check MVP pattern which gives perfect solution for your problem. See below is an example of using synchronous call (I've not tested it, but it should work).
public List<Contacts> getContacts(){
RestAdapter adapter = new RestAdapter.Builder().setEndpoint(ROOT_URL).build();
ContactsAPI api = adapter.create(ContactsAPI.class);
return api.getContacts();
}
See this tutorial to get more idea. I suggest you to move to retrofit 2 which is much more greater than retrofit 1.

Android - Access Servlet AsyncPost task response from fellow classes

I have an app connected to a Java Servlet backend by means of an AsyncPost task. The task returns a String to the client representing a json object serialized with Gson.
It works almost fine, the problem is that I'm unable to access the Servlet response message from the class instantiating the call to the ServletPostAsyncTask: ListViewPrenota.class.
The project is structured as follows:
Both within the Servlet and the Client I created the two classes, Tour.class and Tours.class to store my data:
Tour class:
public class Tour {
// some simple int/string/list fields
}
Tours class:
public class Tours {
private List<Tour> tours;
// ...
}
On Client side, in a ServletPostAsyncTask.class, I receive the aforementioned Gson object within doInBackGround(). Within onPostExecute() I deserialize it, this way:
class ServletPostAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
private Context context;
Tours tours;
#Override
protected String doInBackground(Pair<Context, String>... params) {
//connect to Servlet and get the serialized Gson object
}
#Override
protected void onPostExecute(String jsonResponse) {
tours = (new Gson().fromJson(jsonResponse, Tours.class));
}
}
Now, from ListViewPrenota.class in Client I am calling the ServletPostAsyncTask:
ServletPostAsyncTask s = new ServletPostAsyncTask();
s.execute(new Pair<Context, String>(ListViewPrenota.this, "tours"));
Tours ttours = s.tours;
Tour tour = ttours.getTours().get(0);
Problem: I receive a java.lang.NullPointerException pointing to Tour tour = ttours.getTours().get(0);
What is the reasong preventing me to access the newly received Tours object from other classes than ServletPostAsyncTask?
Thank you very much
the problem is you are thinking that code runs serially, if you want to use stuff returned from the AsycTask you need to use it in onPostExecute or have a callback that sends the data after it is done
doInBackground(){
//do heavy work
}
onPostExecute(Data data){
//handle data
//send data via interface to activity or class that needs the data
//or just put everything that needs the data in here
}
Ok, it works. Here's what I was able to come up with:
Callback interface:
interface CallBack {
void callBackMethod(Tours tours);//do job
}
Caller class:
class ServletPostAsyncTask extends AsyncTask<Pair<Context, String>, Tours, String>{
private Context context;
Tours tours;
public ListViewPrenota listViewPrenota;
public ServletPostAsyncTask(ListViewPrenota listView){
this.listViewPrenota = listView;
}
#Override
protected String doInBackground(Pair<Context, String>... params) {
//communicate with Servlet and get a HttpResponse
}
#Override
protected void onPostExecute(String jsonResponse) {
tours = (new Gson().fromJson(jsonResponse, Tours.class));
//the callback starts a thread updating the UI in ListViewPrenota
listViewPrenota.callBackMethod(tours);
Toast.makeText(
context,
"Connected. \nTours size: "+ tours.getTours().size(),
Toast.LENGTH_LONG).show();
}
}
The callback interface's implementation within ListViewPrenota:
public class ListViewPrenota extends FragmentActivity implements CallBack{
private ProgressDialog m_ProgressDialog = null;
private Runnable viewOrders;
private TourAdapter m_adapter;
ListView listView;
private ArrayList<Tour> m_tours =null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view_prenota);
listView = (ListView) findViewById(R.id.list);
m_tours = new ArrayList<Tour>();
m_adapter = new TourAdapter(this, R.layout.list_row, m_tours);
listView.setAdapter(m_adapter);
getActionBar().setDisplayHomeAsUpEnabled(true); //pulsante drawer
getActionBar().setHomeButtonEnabled(true); //pulsante dietro
ServletPostAsyncTask spat = new ServletPostAsyncTask(ListViewPrenota.this);
String status = spat.getStatus().toString();
spat.execute(new Pair<Context, String>(ListViewPrenota.this,"tours"));
}
public void callBackMethod(final Tours tours){
System.out.println("I've been called back");
viewOrders = new Runnable(){
#Override
public void run() {
getOrders(tours);
}
};
Thread thread = new Thread(null, viewOrders, "MagentoBackground");
thread.start();
m_ProgressDialog = ProgressDialog.show(
ListViewPrenota.this,
"Please wait...",
"Retrieving data ...",
true);
}
public void getOrders(Tours tours){
try{
m_tours = new ArrayList<>();
m_tours.addAll(tours.getTours());
Thread.sleep(2000);
Log.i("ARRAY", "" + m_tours.size());
} catch (Exception e) {
Log.e("BACKGROUND_PROC", e.getMessage());
}
//add tours to the adapter
runOnUiThread(returnRes);
}
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if(m_tours != null && m_tours.size() > 0){
m_adapter.notifyDataSetChanged();
for(int i=0;i<m_tours.size();i++)
m_adapter.add(m_tours.get(i));
}
m_ProgressDialog.dismiss();
m_adapter.notifyDataSetChanged();
}
};
If there's a better way to do it I accept further suggestions.
In the mean time, thank you very much

Categories