I am trying to write to a local variable inside a JSONObjectRequest. Here is my code:
JsonObjectRequest get_id_request = new JsonObjectRequest(Request.Method.GET, URL_ID, null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
boolean load_full_data = false;
try {
JSONObject jsonNotificationID = response.getJSONObject("n");
int notificationID = jsonNotificationID.getInt("id");
// Change flag to get full preferences below
if(notificationID > currentNotificationID) {
load_full_data = true;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onErrorResponse(VolleyError error) {
}
});
I want to be able to check the server, if there are new IDs (different to the ones already stored in shared Preferences), then download the new ones. So to do this, I want to set my variable load_full_data = true, then further down (oustide this request):
// Get the IDs, see if they are different.
volleyQueue.add(get_id_request);
if(load_full_data) {
Log.d(TAG, "run: Load Full Data");
volleyQueue.add(get_full_request);
}
Only thing is, I cant reference a local variable inside my JSONObjectRequest. It says it needs to final. How can I pass data in and out of this?
You could create a new class and let it implement the interface whose definition you would like to enrich, in your case Response.Listener<JSONObject>
I am not familiar with this API but an example code would be like:
class MyResponseListener implements Response.Listener<JSONObject> {
boolean isGoodParam;
MyResponseListener(boolean isGoodParam) {
this.isGoodParam = isGoodParam;
}
public isGoodParam() {
return this.isGoodParam;
}
#Override
public void onResponse(JSONObject response) {
//use your param
if(this.isGoodParam) {
doStuff();
}
}
}
then your client code would be:
boolean initialIsGood = true;
MyResponseListener listener = new MyResponseListener(initialIsGood);
JsonObjectRequest getIdRequest = new JsonObjectRequest(Request.Method.GET, URL_ID, null, listener,
Response.ErrorListener { error ->
// TODO: Handle error
});
//outside of the listener, assuming that the status of the boolean changed and you want to find out the new value
boolean newValue = listener.isGoodParam();
Cosmetic note: Please stick to the code convention standards, makes the code more readable. (for example camelCases and no_snakes :)
Related
View model has been initialized by the following code inside fragment.
viewModel.getContacts(pageNumber, AppConstants.DIRECTION).observe(getActivity(), list -> {
adapter.submitList(list);
});
where viewModel.getContacts() method calls a repository method which in turn makes the web request and brings the response back.
public MutableLiveData<List<Contact>> getAllContacts(int page, String sortedBy) {
return repository.getAllContacts(page, sortedBy);
}
where repository.getAllContacts() method is
public MutableLiveData<List<Contact>> getAllContacts(int page, String orderBy) {
if (allContacts == null) {
allContacts = new MutableLiveData<>();
}
//we will load it asynchronously from server in this method
loadContacts(page, orderBy);
return allContacts;
}
private void loadContacts(int page, String orderBy) {
Call<ContactsResponse> call = bearerApiInterface.getContacts(page, orderBy);
call.enqueue(new Callback<ContactsResponse>() {
#Override
public void onResponse(Call<ContactsResponse> call, Response<ContactsResponse> response) {
Timber.e("Contacts Response => " + new GsonBuilder().setPrettyPrinting().create().toJson(response.body()));
//finally we are setting the list to our MutableLiveData
allContacts.setValue(response.body().getResult().getData());
}
#Override
public void onFailure(Call<ContactsResponse> call, Throwable t) {
}
});
}
And here is my recycler view scroll listener
recyclerView.setOnScrollListener(new EndlessRecyclerOnScrollListener(linearLayoutManager) {
#Override
public void onLoadMore(int current_page) {
loadNextPage();
}
});
Upon scrolling when loadNextPage() gets called, how viewModel.getContacts() could be triggered from loadNextPage() method.
What are the options to send the call again with incremented page number and observe it with same viewModel.getContacts() method. Paging list adapter is not an option for now as the response needs to be updated, deleted & customized while paging list adapter isn't doing that without datasource and snapshot inclusion which isn't working (any help with that would be very helpful if it is possible).
And below is the code for deleting any item from recycler view.
#Override
public void onItemDelete(RecyclerView.ViewHolder viewHolder, int position) {
mActivity.showProgressBar(true);
Timber.e("Delete the contact at position " + position);
viewModel.deleteContact(adapter.getContactAt(viewHolder.getAdapterPosition()).getId(), adapter.getContactAt(viewHolder.getAdapterPosition())).observe(this, new Observer<Boolean>() {
#Override
public void onChanged(Boolean isSuccess) {
if (isSuccess) {
mActivity.showErrorDialog("Contact Deleted Successfully", null, null);
listAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
} else {
mActivity.showErrorDialog("Something went wrong, please try again", null, null);
}
}
});
}
The view model delete method is
public MutableLiveData<Boolean> deleteContact(int id, Contact contact) {
return repository.deleteThisContact(id, contact);
}
And the repository delete method is
public MutableLiveData<Boolean> deleteThisContact(int contactId, Contact contact) {
if (deleteContact == null)
deleteContact = new MutableLiveData<>();
callDeleteContact(contactId, contact);
return deleteContact;
}
private void callDeleteContact(int contactId, Contact contact) {
Call<JsonObject> call = bearerApiInterface.deleteContact(contactId);
call.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful() && response.code() == 200) {
Timber.e("***** Contact Deleted Successfully => " + new GsonBuilder().setPrettyPrinting().create().toJson(response.body()));
delete(contact);
deleteContact.setValue(true);
} else {
try {
deleteContact.setValue(false);
String errorMessage = new APIError().extractMessage(new JSONObject(response.errorBody() != null ? response.errorBody().string().trim() : null));
Timber.e("***** Error message is => " + errorMessage);
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
deleteContact.setValue(false);
Timber.e("***** onFailure" + "response: " + t.getMessage());
}
});
}
Any related code which might be worthy of sharing can be asked. Skipped for simplicity.
You will need to implement the android paging:
First, you have to add on gradle the paging lib:
implementation 'androidx.paging:paging-runtime:2.1.0'
Your data source must extend the PageKeyedDataSource, so, you have to implement 3 methods, loadInitial, loadAfter and loadBefore
On your view model you must create a pager config variable, like:
private val config: PagedList.Config = PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(PAGE_SIZE_HINT)
.setEnablePlaceholders(false)
.build()
It will set up how the pager must be executed, and do you have to create an executor to load the data:
private val executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE)
And after all, create a livedata to receive the list:
val your_source: LiveData<PagedList<YourSource>> = LivePagedListBuilder(dataFactory, config)
.setFetchExecutor(executor)
.build()
Your recycler view adapter must be changed to a PagedListAdapter instead.
I recommend this article:
https://androidwave.com/pagination-in-recyclerview/
I am using the Facebook graph api to find out what pages a user is apart of. When the query comes back with a json object it has what I need but, for some reason it doesn't want to add to my array list. The correct value is printed in log.d it seems to skip my arraylist for some reason. Any ideas?
Find page function
private ArrayList<String> foundPages;
private JSONObject jsonObject;
public ArrayList<String> findPages()
{
accessToken = AccessToken.getCurrentAccessToken();
foundPages = new ArrayList<>();
GraphRequest request = GraphRequest.newGraphPathRequest(
accessToken,
"/me/accounts",
new GraphRequest.Callback() {
#Override
public void onCompleted(GraphResponse response) {
try {
jsonObject = response.getJSONObject();
for(int i=0; i < jsonObject.getJSONArray("data").length(); i++)
{
page = response.getJSONObject().getJSONArray("data").getJSONObject(i).getString("name");
Log.d("viewmodel",page);
foundPages.add(page);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
request.executeAsync();
return foundPages;
}
There is a common way to solve this problem, which is to define a callback method which will return these values to you, AFTER they have been populated by the call, which goes something like this (my java is rusty, bear with me...)
define an interface :
interface Callback{
void apiResponseCallback(ArrayList<Page> result);//whatever your model is, make the array of that type
}
then, in your normal findPages method, change it to this:
public void findPages(Callback callback) {
//
//
........
for(int i=0; i < jsonObject.getJSONArray("data").length(); i++)
{
page = response.getJSONObject().getJSONArray("data").getJSONObject(i).getString("name");
Log.d("viewmodel",page);
foundPages.add(page);
}
callback.apiResponseCallback(foundPages);//here we are returning the data when it is done
}
then, when you call findPages
findPages(new Callback() {
#Override
public void apiResponseCallback(ArrayList<Page> result) {
here, this result parameter that comes through is your api call result to use, so result will be your populated pages to use.
}
});
}
sake of completeness:
public void findPages(Callback callback)
{
accessToken = AccessToken.getCurrentAccessToken();
foundPages = new ArrayList<>();
GraphRequest request = GraphRequest.newGraphPathRequest(
accessToken,
"/me/accounts",
new GraphRequest.Callback() {
#Override
public void onCompleted(GraphResponse response) {
try {
jsonObject = response.getJSONObject();
for(int i=0; i < jsonObject.getJSONArray("data").length(); i++)
{
page = response.getJSONObject().getJSONArray("data").getJSONObject(i).getString("name");
Log.d("viewmodel",page);
foundPages.add(page);
}
callback.apiResponseCallback(foundPages);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
request.executeAsync();
}
Yep. This here:
request.executeAsync();
triggers an asynchronous request. But your "current" thread simply continues to do:
return foundPages;
and it returns an empty list.
That list gets later filled, but at the moment in time when that method returns, that list is still empty. Or just gets filled. Who knows, as it gets filled asynchronously, at some unknown point in the future.
A solution could be to have some other variable/field that tells you the data has arrived and pushed into the list.
Alternatively, that method could just make a synchronous request, simply block the caller from progressing until the data has arrived.
You see, you can't have it both ways: when you don't wait for your results to arrive, you shouldn't expect them to be available immediately.
I'm using socket.io for my chat app. I have an ArrayList which contains last message, username, time. Whenever a new message arrives in JSON format then it should check if JSON contained username is present in ArrayList or not. If present, then updates the ArrayList otherwise add in ArrayList.
Here is my code:-
private Emitter.Listener handle1 = new Emitter.Listener() {
#Override
public void call(final Object... args) {
ChatLists.this.runOnUiThread(new Runnable() {
#Override
public void run() {
JSONObject data = (JSONObject)args[0];
try {
String sendername = data.getString("sender");
String lastMessage = data.getString("message");
String profileImage = data.getString("Profile");
String token = data.getString("fb_token");
chat_list chat_list = new chat_list(sendername,
profileImage, lastMessage, "0", "", "dummy", token);
if (chat_lists.size()==0){
chat_lists.add(chat_list);
}else {
for (int i=0;i<chat_lists.size();i++){
if (chat_lists.get(i).getContactname().equals(sendername)){
chat_lists.set(i,chat_list);
}else {
chat_lists.add(chat_list)
}
}
}
contactlistAdapter = new ContactlistAdapter(chat_lists);
recyclerView.setAdapter(contactlistAdapter);
contactlistAdapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
};
Well, you can use contains() & set() methods of ArrayList in a logical way to solve your problem like below:-
if(chat_lists.contains(username))
chat_lists.set(indexOf(username), new_username);
else chat_lists.add(new_username);
Try it:
if(chat_lists.contains(chat_list)){
chat_lists.remove(chat_list);
chat_lists.add(chat_list);
} else {
chat_lists.add(chat_list);
}
Read about architecture patterns, for example, MVP.
You need to store your messages somethere (in Model) and update view relative to data.
Also read about RecyclerView, cause of ListView is a little bit deprecated
if (chat_lists.get(i).getContactname().equals(sendername)){
above statement has problem them. It's not getting under your if condition and following the chat_lists.add(chat_list) statement.
Instead equals use ignoreCasequals. If still wont it solve your problem please use debug mode or logs check chat_lists.get(i).getContactname()
and sendername same or not.
public class MainActivity extends AppCompatActivity {
public static final int TRANSMIT_DATA = 1;
public static String string0;
public String temp;//定义全局变量,想要把string0的值传给它。
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadData();
System.out.println("Main output:ID="+Thread.currentThread().getId());
anotherThread();
}
public void anotherThread(){
new Thread(){
public void run() {
System.out.println("anotherThread :ID="+Thread.currentThread().getId());
System.out.println("anotherThread output: Content="+temp);
}
}.start(); //开启一个线程
}
private Handler dataHandler =new Handler(){
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TRANSMIT_DATA:
System.out.println("handleMessage output:ID="+Thread.currentThread().getId());
System.out.println("handleMessage output: Content="+msg.obj);
temp=msg.obj.toString();
break;
default:
break;
}
}
};
public void loadData() {
OkHttpClient okHttpClient = new OkHttpClient();
//构造Request,
//builder.get()代表的是get请求,url方法里面放的参数是一个网络地址
Request.Builder builder = new Request.Builder();
final Map params = new LinkedHashMap();// 请求参数
Request request = builder.get()
.url("https://api.avatardata.cn/Jztk/Query?key=15f9ceafeeb94a2492fd84b8c68a554c&subject=4&model=c1&testType=rand")
.build();
//3将Request封装成call
Call call = okHttpClient.newCall(request);
//4,执行call,这个方法是异步请求数据
call.enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
//失败调用
Log.e("MainActivity", "onFailure: " );
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
//成功调用
Log.e("MainActivity", "onResponse: " );
//获取网络访问返回的字符串
string0 = response.body().string();
System.out.println("Asynchronous Request Output:ID="+Thread.currentThread().getId());
Message message = new Message();
message.obj = string0;
message.what =TRANSMIT_DATA;
dataHandler.sendMessage(message);
}
});
}
}
The picture is about System.out.println
Just like the picture show above: the "anotherThread output: Content=null", I want to Pass information from the main thread to the child thread (in the run method), how can I do it? Try to avoid changing the code of other methods as soon as possible.
Given that you want minimal code changes you could use ThreadLocal , value set in Parent threads ThreadLocal is available for all child threads
I think your "otherthread" starts and ends before the data is available in the temp variable, hence it prints null.
You can do something like:
a. Either you start/run your "otherthread" after you fill the temp variable in the handleMessage function.
b. Or if you insist on starting the "otherthread" before you have the data, have the thread in a synchronized way, check the temp variable for being non null after some interval. Also have some sortof boolean to let the thread know, to exit incase you didnt receive any data.
my 2 cents
I'm trying to get PhotoUrl in this method.
private String getUserPhotoUrl(String vk_id){
final String[] url = new String[1];
VKRequest request = VKApi.users().get(VKParameters.from(VKApiConst.USER_ID, vk_id,
VKApiConst.FIELDS, "photo_100"));
request.executeWithListener(new VKRequest.VKRequestListener() {
#Override
public void onComplete(final VKResponse response) {
super.onComplete(response);
new Thread(){
#Override
public void run() {
VKList<VKApiUser> User = (VKList<VKApiUser>) response.parsedModel;
url[0] = User.get(0).photo_100;
Log.i("PhotoUrl", url[0]); //working perfect
}}.start();
}});
return url[0];
}
In the Log.i("PhotoUrl", url[0]); it gives not null. I mean normal url. But when I try to return it in return url[0]; part, it gives me null. Any ideas?
Because of concurrency. Your method immediately returns without waiting for any other thread.
The return statement is outside the listener, and thefore returns befofre listener end processing.
So it returns a null