RxAndroid Retrofit how to fetch 2nd list only when 1st list emits are completed? - retrofit2

Take a simple example given 2 lists Categories and Products
I need to sync on remote server list of local categories and store the returned remote_id on category item,
after that start sync on remote server list of products and store foreach remote_id on product item.
All categories need to sync all before start sync products, because
when sync products i need category id given from server
Main goal is
loop for each item on "categories" list
make API call for each item
ONLY when ALL api call are completed (because i need to get all rempote_id before start to sync products), start same loop on "products" list
In my example use the onComplete are wrong as it get fired when all list items are fetched and NOT when all api calls are completed.
How to solve? What operator should use?
... from ApiService.java
// Create category
#FormUrlEncoded
#POST("Category/")
Single<ApiResponse> createCategory(#Field("Description") String description, #Field("ShortDescription") String shortDescription);
#FormUrlEncoded
#POST("Product/")
Single<ApiResponse> createProduct(#Field("ProductCode") String ProductCode,
#Field("Description") String Description,
#Field("ShortDescription") String ShortDescription,
#Field("Quantity") int Quantity,
#Field("MeasurementUnit_ID") int MeasurementUnit_ID,
#Field("SellPrice") int SellPrice,
#Field("Category_ID") int Category_ID,
#Field("Vat_ID") int Vat_ID
);
....
// MainActivity
List<Category> cats = new CategoryOperations(this).getUnsynced();
List<Product> prods = new ProductOperations(this).getUnsynced();
Observable.fromIterable(cats)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Category>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Category c) {
apiService.createCategory(c.getDescription(), c.getDescription())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<ApiResponse>() {
#Override
public void onSuccess(ApiResponse resp) {
if (resp.id > 0 && resp.success) {
//Toast.makeText(getApplicationContext(), "Created category id: " + resp.id, Toast.LENGTH_LONG).show();
Log.d(TAG, "Created category id: " + resp.id );
c.setCloudId(resp.id);
new CategoryOperations(ctx).update(c);
return;
}else{
Log.d(TAG, "Category not created " + resp.message);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
}
);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Log.d(TAG, "All categories synced");
Log.d(TAG, "Now can start to sync products");
Log.d(TAG, "Obvioussly error here as this complete only fired when all items are fetched and NOT");
Log.d(TAG, "When all api calls are completed");
}
});

Related

BroadcastReceiver triggered to update another Activity

I am trying to make a feature as part of my android app, where a user interacts with a geofence based on their location on a map and it will fire up a dialog telling the user they are near the starting point of the route using a BroadcastReceiver in its own class.
So far I can trigger it and provide Toast messages, but I can't seem to use it to trigger a UI change in my other activity.
Here is my BroadcastReceiver class -
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
private static final Object TAG = "Error";
#Override
public void onReceive(Context context, Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
Log.d("TOASTY", "onReceive: Geofence even has error..");
}
List<Geofence> triggeredGeofenceList = geofencingEvent.getTriggeringGeofences();
for (Geofence geofence : triggeredGeofenceList) {
Log.d("GEOF", "onReceive: "+geofence.getRequestId());
}
Location triggerLocation = geofencingEvent.getTriggeringLocation();
double lat = triggerLocation.getLatitude();
double lon = triggerLocation.getLongitude();
Toast.makeText(context, "GEOFENCE TRIGGERED AT : LAT IS :" + lat + " LON IS : " +lon, Toast.LENGTH_SHORT).show();
int transitionType = geofencingEvent.getGeofenceTransition();
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
Toast.makeText(context, "Entered Geofence", Toast.LENGTH_SHORT).show();
Log.d("GEOF", "onReceive: "+geofencingEvent.getGeofenceTransition());
break;
case Geofence.GEOFENCE_TRANSITION_DWELL:
Toast.makeText(context, "Dwelling inside of Geofence", Toast.LENGTH_SHORT).show();
break;
case Geofence.GEOFENCE_TRANSITION_EXIT:
Toast.makeText(context, "Exited Geofence area", Toast.LENGTH_SHORT).show();
break;
}
Bundle b = intent.getExtras();
Intent i = new Intent(context, routeActivity.class);
i.putExtra("lat", lat);
i.putExtra("lon", lon);
i.putExtras(b);
Log.d("LOLCALLY", "onReceive: "+i);
context.sendBroadcast(i);
}
}
My thinking was to use intent, I have tried to pull the triggered location (which I can see is correct in the log output) into my other activity but no joy.
Many thanks!
You need to register your receiver on your activity and process its callback:
public class MyActivity extends AppCompatActivity {
private BroadcastReceiver geofenceReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Pull triggered location and use it to update the activity
}
};
#Override
protected void onResume() {
super.onResume();
registerReceiver(geofenceReceiver, new IntentFilter("YOUR_GEOFENCE_ACTION"));
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(geofenceReceiver);
}
}

RxAndroid onNext being called multiple times

I am calling an api via retrofit with RxAndroid. Here's how the method is defined
#GET("product")
Observable<BaseResponse<List<Product>>> getProducts(#Query("lang") String lang,
#Query("category_id") String category_id,
#Query("start") String start,
#Query("count") String count);
This api returns the list of Product. I wanted to process each Product object inside list to check if that product exists in local db & set the boolean in product object. So I did it like this,
public Observable<List<Product>> getProductList(String catId, int start, int count) {
final List<Product> products = new ArrayList<>();
return RestClient.callApiWrapper(mContext, true, null,
RestClient.get().getProducts("en", catId, "" + start, "" + count))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMapIterable(new Func1<BaseResponse<List<Product>>, Iterable<Product>>() {
#Override
public Iterable<Product> call(BaseResponse<List<Product>> listBaseResponse) {
Log.e("TAG", "flatMapIterable called");
return listBaseResponse.getData();
}
})
.map(new Func1<Product, List<Product>>() {
#Override
public List<Product> call(Product product) {
Log.e("TAG", "map called => " + products.size());
products.add(checkIfProductAddedToCart(product));
return products;
}
});
}
I have subscribed to getProductList where I need the processed list in this way:
mProductListDataModel.getProductList(mCategoryId, mStartOffset, mCount)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Product>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Product> products) {
Log.e("TAG", "onNext => " + products.size());
if(mOnProductListLoaded != null)
mOnProductListLoaded.onProductListLoaded(products);
}
});
Now the problem is, the API actually returns list of 5 products, but onNext is being called 5 times each time with increasing size upto 40 products. Please let me know where I am doing wrong.
I have changed few things for now, which are as below which works for me:
changed getProductList to return Product instead of List<Product>
so it looks like
public Observable<Product> getProductList(String catId, int start, int count) {
//final List<Product> products = new ArrayList<>();
return RestClient.callApiWrapper(mContext, true, null,
RestClient.get().getProducts("en", catId, "" + start, "" + count))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<BaseResponse<List<Product>>, Observable<Product>>() {
#Override
public Observable<Product> call(BaseResponse<List<Product>> listBaseResponse) {
return Observable.from(listBaseResponse.getData());
}
})
.map(new Func1<Product, Product>() {
#Override
public Product call(Product product) {
//Log.e("TAG", "map called => " + products.size());
//products.add(checkIfProductAddedToCart(product));
//return products;
return checkIfProductAddedToCart(product);
}
});
}
and changed following where this observable was subscribed:
final List<Product> products = new ArrayList<>();
mProductListDataModel.getProductList(mCategoryId, mStartOffset, mCount)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Product>() {
#Override
public void onCompleted() {
Log.e("TAG", "onCompleted => " + products.size());
if (mOnProductListLoaded != null)
mOnProductListLoaded.onProductListLoaded(products);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Product product) {
products.add(product);
}
});
However, I am still not sure if this is the right way of doing this. Please let me know if this is not the right way to do so.

Creating a very custom list

I'm developping an application, and now, I don't know what to do next:
I have a list of elements, each element has some informations + an ID + a logo.
What I want to do is creating a list like in the picture
List
Of course, I want it in a single layer, with the logo, some informations, and a button to define an action; where I could use the ID of the selected item.
I did some research, may be I found some relative subjects, but none of what I want.
My list is a
ArrayList<ArrayList<String>>
filled by data from database.
Thank you
Here it is:
public class Avancee extends Activity {
// Log tag
private static final String TAG = MainActivity2.class.getSimpleName();
// Movies json url
private static final String url = "http://blabla.com/movie.json";
private ProgressDialog pDialog;
private List<Movie> movieList = new ArrayList<Movie>();
private ListView listView;
private CustomListAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
adapter = new CustomListAdapter(this, movieList);
listView.setAdapter(adapter);
pDialog = new ProgressDialog(this);
// Showing progress dialog before making http request
pDialog.setMessage("Loading...");
pDialog.show();
// changing action bar color
getActionBar().setBackgroundDrawable(
new ColorDrawable(Color.parseColor("#1b1b1b")));
// Creating volley request obj
JsonArrayRequest movieReq = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
//Log.d(TAG, response.toString());
hidePDialog();
String result = getIntent().getStringExtra("ITEM_EXTRAA");
System.out.println(result);
try{
JSONArray ja = new JSONArray(result);
for (int i = 0; i < ja.length(); i++) {
try {
JSONObject obj = ja.getJSONObject(i);
Movie movie = new Movie();
movie.setTitle(obj.getString("title"));
movie.setLocation(obj.getString("location_search_text"));
movie.setId(obj.getInt("id"));
// adding movie to movies array
movieList.add(movie);
} catch (JSONException e) {
e.printStackTrace();
}
}
}catch(JSONException e){
Log.e("log_tag", "Error parsing data "+e.toString());
}
// notifying list adapter about data changes
// so that it renders the list view with updated data
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
VolleyLog.d(TAG, "Error: " + error.getMessage());
hidePDialog();
}
});
// Adding request to request queue
AppController.getInstance().addToRequestQueue(movieReq);
}
#Override
public void onDestroy() {
super.onDestroy();
hidePDialog();
}
private void hidePDialog() {
if (pDialog != null) {
pDialog.dismiss();
pDialog = null;
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
It's the "id" that I want to get in the OnClick event.
use this code
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Movie movie= movieList.get(position);
}
});
//here position will give you the id of listview cell so you can use it like
Movie movie= movieList.get(position);
then you can use it get all the data inside your moview object

The content of the adapter has changed but ListView did not receive a notification.

I want to show detected beacon UUID, Major and Minor in the list view. When my onServiceConnect() method is called, it will add items(UUID, Major, Minor values) in the list. Although it shows the list items when i debug my code but my app terminates after a few seconds leaving me with this error:
The content of the adapter has changed but ListView did not receive a
notification. Make sure the content of your adapter is not modified
from a background thread, but only from the UI thread. Make sure your
adapter calls notifyDataSetChanged() when its content changes.
Here's my code
public class NotifyDemo_Sc4 extends Activity implements BeaconConsumer {
private ListView list;
private ArrayList<String> arrayList;
Button buttonNotify,b2;
private BeaconManager beaconManager;
private BluetoothAdapter mBluetoothAdapter;
private static final String TAG = "MainActivity";
private int nos_calls=0;
private boolean run_call=true;
Beacon beacon;
ArrayAdapter<String> adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notifydemo);
WebView view = (WebView) findViewById(R.id.myWebView3);
buttonNotify = (Button) findViewById(R.id.buttonViewNotific);
b2 = (Button) findViewById(R.id.buttondemo);
b2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent i = new Intent(NotifyDemo_Sc4.this, HomeScreen.class);
startActivity(i);
}
});
view.loadUrl("file:///android_asset/name.html");
list = (ListView) findViewById(R.id.listview2);
arrayList = new ArrayList<String>();
adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_spinner_item, arrayList);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
runOnUiThread(new Runnable() {
public void run() {
adapter.notifyDataSetChanged();
}
});
// requestLayout();
beaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(this);
beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
beaconManager.bind(this);
beaconManager.setDebug(true);
beaconManager.setBackgroundScanPeriod(1100l);
beaconManager.setAndroidLScanningDisabled(false);
Log.d(TAG, "didEnterRegion" + beaconManager);
mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
// Create the adapter to convert the array to views
// ArrayAdapter adapter = new ArrayAdapter<>(this, arrayOfDevices);
// Attach the adapter to a ListView
}
#Override
protected void onDestroy() {
super.onDestroy();
beaconManager.unbind(this);
}
#Override
public void onBeaconServiceConnect() {
blefunction();
// nos_calls++;
final Region region = new Region("myBeaons", Identifier.parse("E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"), null, null);
// runOnUiThread(new Runnable() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
beaconManager.setRangeNotifier(new RangeNotifier() {
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
for (Beacon beacon : beacons) {
beacon = beacons.iterator().next();
String b = beacon.toString();
double b2 = beacon.getDistance();
Log.i(TAG, "The first beacon I see is about " + beacon.toString() + " Distance " + beacon.getDistance() + " meters away.");
Log.i(TAG, "Beacon Detected" + beacon.toString());
arrayList.add("Beacon UUID, Major, Minor:" + b + "\n");
Toast.makeText(getApplicationContext(), " The beacon " + "The distance " + beacon.getDistance() + " meters away.", Toast.LENGTH_SHORT);
}
}
});
}}, 3000);
try
{
beaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", Identifier.parse("E2C56DB5-DFFB-48D2-B060-D0F5A71096E0"), Identifier.parse("0"), Identifier.parse("1")));
}
catch(RemoteException e){}
}
void blefunction()
{if(run_call = true){
mBluetoothAdapter.startLeScan(new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
Log.d(TAG, "Scanned BLE device with mac: " + device.getAddress());
String add = "C4:BE84:05:EE:BF";
long e = System.currentTimeMillis();
Log.i(TAG, "in " + (e) + "ms");
// Toast.makeText(getApplicationContext(), "BLE Scanned Device Address: " + device.getAddress() + "/n" + "Time in milliseconds:" + e + " ms", Toast.LENGTH_SHORT).show();
run_call = false; }
});}}
}
The problem is caused by manipulating the adapter or the arrayList it contains from any thread that is not the UI thread. There is at least one line that changes the arrayList in a beacon callback, which is not on the UI thread:
arrayList.add("Beacon UUID, Major, Minor:" + b + "\n");
To fix the problem, you need to wrap that line in a block like below. For best efficiency, you should wrap the whole beacon iteration loop in a block like this, so you aren't creating a new Runnable repeatedly inside the loop.
runOnUiThread(new Runnable() {
public void run() {
...
}
});
You also need to call adapter.notifyDataSetChanged(); from the UI thread to have the changes take effect. You should add that to the end of the block.
While this is not part of the question, it is worth noting that the following line inside the loop that iterates over the beacons will set the value of beacon to the first item in the list. This likely defeats the purpose of iterating over beacons, so you may want to remove that line:
beacon = beacons.iterator().next();

How to implement Custom Suggestions in SearchView from fragment

i have a view pager with 3 fragment one of the fragment
have searchview widget and i get to know that i can,t implement the standard search interface so i implement some thing like this but how to implement Custom Suggestions in SearchView
public class LoaderCursor extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
CursorLoaderListFragment list = new CursorLoaderListFragment();
fm.beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
#Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No phone numbers");
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
#Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
#Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
}

Resources