notifyPropertyChanged on MutableRealmInteger - android

I'm using Realm and data binding.
Before MutableRealmInteger, I used regular primitive type and used its setter, and UI changed automatically with the new value.
Now I want to use MutableRealmInteger type, but I don't know how to notify the UI of changes.
This is a part of my model:
public class MyModel extends RealmObject implements Observable {
...
public final MutableRealmInteger NewWayAAA = MutableRealmInteger.valueOf(0);
private String oldWayAAA = "0";
...
#Bindable
public String getOldWayAAA() {
return oldWayAAA.isEmpty() ? "0" : oldWayAAA;
}
public void setOldWayAAA (String param) {
if (!param.isEmpty()) {
this.oldWayAAA= param;
notifyPropertyChanged(BR.oldwayaaa);
}
}
...
/**
* Notifies listeners that all properties of this instance have changed.
*/
public synchronized void notifyChange() {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, 0, null);
}
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with {#link Bindable} to generate a field in
* <code>BR</code> to be used as <code>fieldId</code>.
*
* #param fieldId The generated BR id for the Bindable field.
*/
public void notifyPropertyChanged(int fieldId) {
if (mCallbacks != null) {
mCallbacks.notifyCallbacks(this, fieldId, null);
}
}
#Override
public void addOnPropertyChangedCallback(OnPropertyChangedCallback onPropertyChangedCallback) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
mCallbacks.add(onPropertyChangedCallback);
}
#Override
public void removeOnPropertyChangedCallback(OnPropertyChangedCallback onPropertyChangedCallback) {
if (mCallbacks != null) {
mCallbacks.remove(onPropertyChangedCallback);
}
}
MutableRealmInteger doesn't have setter and the usage of it is:
myModelIns.NewWayAAA.increment(10);

public class MyModel extends RealmObject implements Observable {
...
private final MutableRealmInteger NewWayAAA = MutableRealmInteger.valueOf(0);
...
#Bindable
public int getNewWayAAA() {
return NewWayAAA.getValue();
}
public void incrementNewWayAAA() {
NewWayAAA.increment();
notifyPropertyChanged(BR.newwayaaa);
}
...

Related

How to store parametrized listeners in list?

I have a listener:
public interface OnCompleteListener<T> {
void onComplete(T data);
}
I store it in list:
private List<OnCompleteListener<?>> mListeners = new ArrayList<>();
// ...
public void addType1Listener() {
addListener(new OnCompleteListener<Type1>() {
//...
});
}
public void addType2Listener() {
addListener(new OnCompleteListener() {
//...
});
}
private <T> void addListener(OnCompleteListener<T> listener) {
mListeners.add(listener);
}
I am trying to get it by this way:
public <T> OnRequestsCompleteListener<T> get(int i) {
return (OnRequestsCompleteListener<T>) mListeners.get(i);
}
Type1 and Type2 have no parent class and cannot have.
But I get 'unchecked cast' warning. How to get it correctly?
Instead of specifying genetic T in a method, you need a class with generic T
public class ListenerCollection <T> {
private List<OnCompleteListener<T>> mListeners = new ArrayList<OnCompleteListener<T>>();
public void addListener(OnCompleteListener<T> listener) {
mListeners.add(listener);
}
public OnCompleteListener<T> get(int i) {
return mListeners.get(i);
}
}
Suppose you have a class OnRequestCompleteListener, implemening OnCompleteListener<String>. Then you do:
ListenerCollection<String> lcollection;
...........
OnRequestCompleteListener newListener = (OnRequestCompleteListener) lcollection.get(i);
That doesn't give any warning.

Change Observable List to Hashmap Singleton

I have a singleton to cache objects, from here I create an observable from a List, this List is a response from the API which is filled with objects. (JSON)
private static BehaviorSubject<List<Model>> observableModelsList;
private static Observable<List<Model>> observable = myAPI.loadModelsRx();
private static Subscription subscription;
private PoiSingleton() {
}
public static PoiSingleton getInstance() {
return ourInstance;
}
public static void resetObservable() {
observablePoisList = BehaviorSubject.create();
if (subscription != null && !subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
subscription = observable.subscribe(new Subscriber<List<Model>>() {
#Override
public void onCompleted() {
// do nothing
}
#Override
public void onError(Throwable e) {
observablePoisList.onError(e);
}
#Override
public void onNext(List<Model> models) {
observablePoisList.onNext(models);
}
});
}
public static Observable<List<Poi>> getPoisObservable() {
if (observablePoisList == null) {
resetObservable();
}
return observablePoisList;
}
What I want to achieve is create a HashMap from the List, the key should be the ID of the object and the value the object itself.
I am new to Android and Retrofit/RxJava so in what method/stage is it responsible to create the HashMap?

Shopify Android API pulling product images to ListView

Im making an app for a Shopify website using the Shopify Android API and currently the sample app just lists each product in a simple text ListView. Can anyone aid me in trying to get the images and display them next to the text in the ListView. Any help would be amazing, sorry kinda new to this.
ProductListActivity.java
public class ProductListActivity extends SampleListActivity {
static final String EXTRA_COLLECTION_ID = "ProductListActivity.EXTRA_COLLECTION_ID";
private String collectionId;
private ProductDetailsTheme theme;
private boolean useProductDetailsActivity;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.product_list_activity);
setTitle(R.string.choose_product);
useProductDetailsActivity = true;
theme = new ProductDetailsTheme(getResources());
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_COLLECTION_ID)) {
collectionId = intent.getStringExtra(EXTRA_COLLECTION_ID);
}
theme.setStyle(ProductDetailsTheme.Style.LIGHT);
theme.setShowProductImageBackground(false);
}
#Override
protected void onResume() {
super.onResume();
// If we haven't already loaded the products from the store, do it now
if (listView.getAdapter() == null && !isFetching) {
isFetching = true;
showLoadingDialog(R.string.loading_data);
Callback<List<Product>> callback = new Callback<List<Product>>() {
#Override
public void success(List<Product> products, Response response) {
isFetching = false;
dismissLoadingDialog();
onFetchedProducts(products);
}
#Override
public void failure(RetrofitError error) {
isFetching = false;
onError(error);
}
};
if (collectionId != null) {
getSampleApplication().getProducts(collectionId, callback);
} else {
getSampleApplication().getAllProducts(callback);
}
}
}
/**
* Once the products are fetched from the server, set our listView adapter so that the products appear on screen.
*
* #param products
*/
private void onFetchedProducts(final List<Product> products) {
runOnUiThread(new Runnable() {
#Override
public void run() {
List<String> productTitles = new ArrayList<String>();
for (Product product : products) {
productTitles.add(product.getTitle());
}
listView.setAdapter(new ArrayAdapter<>(ProductListActivity.this, R.layout.simple_list_item, productTitles));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (useProductDetailsActivity) {
launchProductDetailsActivity(products.get(position));
} else {
createCheckout(products.get(position));
}
}
});
}
});
}
private void launchProductDetailsActivity(Product product) {
getSampleApplication().launchProductDetailsActivity(this, product, theme);
}
/**
* When the user selects a product, create a new checkout for that product.
*
* #param product
*/
private void createCheckout(final Product product) {
showLoadingDialog(R.string.syncing_data);
getSampleApplication().createCheckout(product, new Callback<Checkout>() {
#Override
public void success(Checkout checkout, Response response) {
dismissLoadingDialog();
onCheckoutCreated(checkout);
}
#Override
public void failure(RetrofitError error) {
onError(error);
}
});
}
/**
* If the selected product requires shipping, show the list of shipping rates so the user can pick one.
* Otherwise, skip to the discounts activity (gift card codes and discount codes).
*
* #param checkout
*/
private void onCheckoutCreated(Checkout checkout) {
if (checkout.isRequiresShipping()) {
startActivity(new Intent(ProductListActivity.this, ShippingRateListActivity.class));
} else {
startActivity(new Intent(ProductListActivity.this, DiscountActivity.class));
}
}
}
SampleListActivity.java
public class SampleListActivity extends SampleActivity {
protected ListView listView;
protected boolean isFetching;
#Override
protected void onCreate(Bundle savedInstanceState) {
onCreate(savedInstanceState, R.layout.list_activity);
}
protected void onCreate(Bundle savedInstanceState, int layoutId) {
super.onCreate(savedInstanceState);
setContentView(layoutId);
listView = (ListView) findViewById(R.id.list_view);
isFetching = false;
}
}
SampleActivity.java
public class SampleActivity extends AppCompatActivity {
private static final String LOG_TAG = SampleActivity.class.getSimpleName();
// The amount of time in milliseconds to delay between network calls when you are polling for Shipping Rates and Checkout Completion
protected static final long POLL_DELAY = 500;
protected Handler pollingHandler;
private ProgressDialog progressDialog;
private boolean webCheckoutInProgress;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pollingHandler = new Handler();
initializeProgressDialog();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
#Override
protected void onResume() {
super.onResume();
// If we are being launched by a url scheme, check the scheme and retrieve the checkout token if provided
Intent intent = getIntent();
Uri uri = intent.getData();
String scheme = getString(R.string.web_return_to_scheme);
if (uri != null && TextUtils.equals(uri.getScheme(), scheme)) {
webCheckoutInProgress = false;
// If the app was launched using the scheme, we know we just successfully completed an order
onCheckoutComplete();
} else {
// If a Web checkout was previously launched, we should check its status
if (webCheckoutInProgress && getSampleApplication().getCheckout() != null) {
pollCheckoutCompletionStatus(getSampleApplication().getCheckout());
}
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if (progressDialog != null) {
progressDialog.dismiss();
}
}
protected SampleApplication getSampleApplication() {
return (SampleApplication) getApplication();
}
/**
* Initializes a simple progress dialog that gets presented while the app is communicating with the server.
*/
private void initializeProgressDialog() {
if (progressDialog != null) {
progressDialog.dismiss();
}
progressDialog = new ProgressDialog(this);
progressDialog.setIndeterminate(true);
progressDialog.setTitle(getString(R.string.please_wait));
progressDialog.setCancelable(true);
progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
SampleActivity.this.finish();
}
});
}
/**
* Present the progress dialog.
*
* #param messageId The identifier (R.string value) of the string to display in the dialog.
*/
protected void showLoadingDialog(final int messageId) {
runOnUiThread(new Runnable() {
#Override
public void run() {
progressDialog.setMessage(getString(messageId));
progressDialog.show();
}
});
}
protected void dismissLoadingDialog() {
runOnUiThread(new Runnable() {
#Override
public void run() {
progressDialog.dismiss();
}
});
}
protected void onError(RetrofitError error) {
onError(BuyClient.getErrorBody(error));
}
/**
* When we encounter an error with one of our network calls, we abort and return to the previous activity.
* In a production app, you'll want to handle these types of errors more gracefully.
*
* #param errorMessage
*/
protected void onError(String errorMessage) {
progressDialog.dismiss();
Log.e(LOG_TAG, "Error: " + errorMessage);
Toast.makeText(this, R.string.error, Toast.LENGTH_LONG).show();
finish();
}
/**
* Use the latest Checkout objects details to populate the text views in the order summary section.
*/
protected void updateOrderSummary() {
final Checkout checkout = getSampleApplication().getCheckout();
if (checkout == null) {
return;
}
((TextView) findViewById(R.id.line_item_price_value)).setText('$' + checkout.getLineItems().get(0).getPrice());
double totalDiscount = 0;
Discount discount = checkout.getDiscount();
if (discount != null && !TextUtils.isEmpty(discount.getAmount())) {
totalDiscount += Double.parseDouble(discount.getAmount());
}
((TextView) findViewById(R.id.discount_value)).setText("-$" + Double.toString(totalDiscount));
double totalGiftCards = 0;
List<GiftCard> giftCards = checkout.getGiftCards();
if (giftCards != null) {
for (GiftCard giftCard : giftCards) {
if (!TextUtils.isEmpty(giftCard.getAmountUsed())) {
totalGiftCards += Double.parseDouble(giftCard.getAmountUsed());
}
}
}
((TextView) findViewById(R.id.gift_card_value)).setText("-$" + Double.toString(totalGiftCards));
((TextView) findViewById(R.id.taxes_value)).setText('$' + checkout.getTotalTax());
((TextView) findViewById(R.id.total_value)).setText('$' + checkout.getPaymentDue());
if (checkout.getShippingRate() != null) {
((TextView) findViewById(R.id.shipping_value)).setText('$' + checkout.getShippingRate().getPrice());
} else {
((TextView) findViewById(R.id.shipping_value)).setText("N/A");
}
}
/**
* Polls until the web checkout has completed.
*
* #param checkout the checkout to check the status on
*/
protected void pollCheckoutCompletionStatus(final Checkout checkout) {
showLoadingDialog(R.string.getting_checkout_status);
getSampleApplication().getCheckoutCompletionStatus(new Callback<Boolean>() {
#Override
public void success(Boolean complete, Response response) {
if (complete) {
dismissLoadingDialog();
onCheckoutComplete();
} else {
pollingHandler.postDelayed(new Runnable() {
#Override
public void run() {
pollCheckoutCompletionStatus(checkout);
}
}, POLL_DELAY);
}
}
#Override
public void failure(RetrofitError error) {
onError(error);
}
});
}
/**
* When our polling determines that the checkout is completely processed, show a toast.
*/
private void onCheckoutComplete() {
dismissLoadingDialog();
webCheckoutInProgress = false;
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(SampleActivity.this, R.string.checkout_complete, Toast.LENGTH_LONG).show();
}
});
}
}
This is probably also useful Shopify API Product
package com.shopify.buy.model;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import com.shopify.buy.dataprovider.BuyClientFactory;
import com.shopify.buy.utils.DateUtility;
import com.shopify.buy.utils.DateUtility.DateDeserializer;
import java.util.ArrayList;
import java.util.Date;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A {#code Product} is an individual item for sale in a Shopify store.
*/
public class Product extends ShopifyObject {
#SerializedName("product_id")
private String productId;
#SerializedName("channel_id")
private String channelId;
private String title;
private String handle;
#SerializedName("body_html")
private String bodyHtml;
#SerializedName("published_at")
private Date publishedAtDate;
#SerializedName("created_at")
private Date createdAtDate;
#SerializedName("updated_at")
private Date updatedAtDate;
private String vendor;
#SerializedName("product_type")
private String productType;
private List<ProductVariant> variants;
private List<Image> images;
private List<Option> options;
private String tags;
private Set<String> tagSet;
private boolean available;
private boolean published;
/**
* #return {#code true} if this product has been published on the store, {#code false} otherwise.
*/
public boolean isPublished() {
return published;
}
/**
* #return The unique identifier for this product.
*/
public String getProductId() {
return productId;
}
/**
* #return The unique identifier of the Mobile App sales channel for this store.
*/
public String getChannelId() {
return channelId;
}
/**
* #return The title of this product.
*/
public String getTitle() {
return title;
}
/**
* #return The handle of the product. Can be used to construct links to the web page for the product.
*/
public String getHandle() {
return handle;
}
/**
* #return The description of the product, complete with HTML formatting.
*/
public String getBodyHtml() {
return bodyHtml;
}
/**
* Use {#link #getPublishedAtDate() getPublishedAtDate()}.
*/
#Deprecated
public String getPublishedAt() {
return publishedAtDate == null ? null : DateUtility.createDefaultDateFormat().format(publishedAtDate);
}
/**
* Use {#link #getCreatedAtDate() getCreatedAtDate()}.
*/
#Deprecated
public String getCreatedAt() {
return createdAtDate == null ? null : DateUtility.createDefaultDateFormat().format(createdAtDate);
}
/**
* Use {#link #getUpdatedAtDate() getUpdatedAtDate()}.
*/
#Deprecated
public String getUpdatedAt() {
return updatedAtDate == null ? null : DateUtility.createDefaultDateFormat().format(updatedAtDate);
}
/**
* #return The date this product was published.
*/
public Date getPublishedAtDate() {
return publishedAtDate;
}
/**
* #return The date this product was created.
*/
public Date getCreatedAtDate() {
return createdAtDate;
}
/**
* #return The date this product was last updated.
*/
public Date getUpdatedAtDate() {
return updatedAtDate;
}
/**
* #return The name of the vendor of this product.
*/
public String getVendor() {
return vendor;
}
/**
* #return The categorization that this product was tagged with, commonly used for filtering and searching.
*/
public String getProductType() {
return productType;
}
/**
* #return A list of additional categorizations that a product can be tagged with, commonly used for filtering and searching. Each tag has a character limit of 255.
*/
public Set<String> getTags() { return tagSet; }
/**
* #return A list {#link ProductVariant} objects, each one representing a different version of this product.
*/
public List<ProductVariant> getVariants() {
return variants;
}
/**
* #return A list of {#link Image} objects, each one representing an image associated with this product.
*/
public List<Image> getImages() {
return images;
}
/**
* #return {code true} if this product has at least one image, {#code false} otherwise.
*/
public boolean hasImage() {
return images != null && !images.isEmpty();
}
/**
* #return A list of {#link Option} objects, which can be used to select a specific {#link ProductVariant}.
*/
public List<Option> getOptions() {
return options;
}
/**
* #return {#code true} if this product is in stock and available for purchase, {#code false} otherwise.
*/
public boolean isAvailable() { return available; }
/**
* For internal use only.
*/
public boolean hasDefaultVariant() {
return variants != null && variants.size() == 1 && variants.get(0).getTitle().equals("Default Title");
}
/**
* Returns the {#code Image} for the {#code ProductVariant} with the given id
* #param variant the {#link ProductVariant} to find the {#link Image}
* #return the {#link Image} corresponding to the {#link ProductVariant} if one was found, otherwise the {#code Image} for the {#link Product}. This may return null if no applicable images were found.
*/
public Image getImage(ProductVariant variant) {
if (variant == null) {
throw new NullPointerException("variant cannot be null");
}
List<Image> images = getImages();
if (images == null || images.size() < 1) {
// we did not find any images
return null;
}
for (Image image : images) {
if (image.getVariantIds() != null && image.getVariantIds().contains(variant.getId())) {
return image;
}
}
// The variant did not have an image, use the default image in the Product
return images.get(0);
}
/**
* #param optionValues A list of {#link OptionValue} objects that represent a specific variant selection.
* #return The {#link ProductVariant} that matches the given list of the OptionValues, or {#code null} if no such variant exists.
*/
public ProductVariant getVariant(List<OptionValue> optionValues) {
if (optionValues == null) {
return null;
}
int numOptions = optionValues.size();
for (ProductVariant variant : variants) {
for (int i = 0; i < numOptions; i++) {
if (!variant.getOptionValues().get(i).getValue().equals(optionValues.get(i).getValue())) {
break;
} else if (i == numOptions - 1) {
return variant;
}
}
}
return null;
}
public static class ProductDeserializer implements JsonDeserializer<Product> {
#Override
public Product deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return fromJson(json.toString());
}
}
/**
* A product object created using the values in the JSON string.
*/
public static Product fromJson(String json) {
Gson gson = BuyClientFactory.createDefaultGson(Product.class);
Product product = gson.fromJson(json, Product.class);
List<ProductVariant> variants = product.getVariants();
if (variants != null) {
for (ProductVariant variant : variants) {
variant.productId = Long.parseLong(product.productId);
variant.productTitle = product.getTitle();
}
}
// Create the tagSet.
product.tagSet = new HashSet<>();
// Populate the tagSet from the comma separated list.
if (!TextUtils.isEmpty(product.tags)) {
for (String tag : product.tags.split(",")) {
String myTag = tag.trim();
product.tagSet.add(myTag);
}
}
return product;
}
}
simple_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:padding="#dimen/spacing_16dp"
android:textColor="#android:color/black"
android:textSize="#dimen/text_large">
</TextView>

method clash from android LoaderManager

Suppose I have an android app with a Feed class, which is called by some Fragment implemented as follows and throwing : java.lang.ClassCastException: com.newsfeeder.ui.MainFragment cannot be cast to android.app.LoaderManager$LoaderCallbacks
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
public class MainFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<Feed>>
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v=inflater.inflate(R.layout.main_container, null);
listView = (ListView) v.findViewById(R.id.list);
getLoaderManager().initLoader(0, null, (android.app.LoaderManager.LoaderCallbacks<Object>) this);
return v;
}
public Loader<List<Feed>> onCreateLoader(int id, Bundle args) {
final List<Feed> initialItems = items;
return new ThrowableLoader<List<Feed>>(getActivity(), items) {
#Override
public List<Feed> loadData() throws Exception {
try {
if(getActivity() != null) {
return serviceProvider.getFeeds(); //some method fetching some `feeds`
} else {
return Collections.emptyList();
}
} catch (OperationCanceledException e) {
Activity activity = getActivity();
if (activity != null)
activity.finish();
return initialItems;
}
}
};
}
protected List<Feed> items = Collections.emptyList();
Btw ThrowableLoader class is implemented as follow
import android.content.Context;
public abstract class ThrowableLoader<D> extends AsyncLoader<D> {
private final D data;
private Exception exception;
/**
* Create loader for context and seeded with initial data
*
* #param context
* #param data
*/
public ThrowableLoader(Context context, D data) {
super(context);
this.data = data;
}
#Override
public D loadInBackground() {
exception = null;
try {
return loadData();
} catch (Exception e) {
Ln.d(e, "Exception loading data");
exception = e;
return data;
}
}
/**
* #return exception
*/
public Exception getException() {
return exception;
}
/**
* Clear the stored exception and return it
*
* #return exception
*/
public Exception clearException() {
final Exception throwable = exception;
exception = null;
return throwable;
}
/**
* Load data
*
* #return data
* #throws Exception
*/
public abstract D loadData() throws Exception;
}
and here's the `AsyncLoader`
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
public abstract class AsyncLoader<D> extends AsyncTaskLoader<D> {
private D data;
/**
* Create async loader
*
* #param context
*/
public AsyncLoader(Context context) {
super(context);
}
#Override
public void deliverResult(D data) {
if (isReset())
// An async query came in while the loader is stopped
return;
this.data = data;
super.deliverResult(data);
}
#Override
protected void onStartLoading() {
if (data != null)
deliverResult(data);
if (takeContentChanged() || data == null)
forceLoad();
}
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
#Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
data = null;
}
}
The problem is you are mixing android.support.v4.app.LoaderManager with android.app.LoaderManager.
I guess you want to use the support library and hence you're using the android.support.v4.app.Fragment class (those imports are missing), therefore you should not do the ((android.app.LoaderManager.LoaderCallbacks<Object>)this) cast but just implement the android.support.v4.app.LoaderManager.LoaderCallbacks<List<Feed>> interface in your android.support.v4.app.Fragment and pass it without cast.

Equivalent of iOS NSNotificationCenter in Android?

Is there an equivalent of the iOS class NSNotificationCenter in Android ? Are there any libraries or useful code available to me ?
In Android there is not a central notification center as in ios.
But you can basically use Observable and Observer objects to achieve your task.
You can define a class like something below, just modify it for singleton use and add synchronized for concurrent use but the idea is the same:
public class ObservingService {
HashMap<String, Observable> observables;
public ObservingService() {
observables = new HashMap<String, Observable>();
}
public void addObserver(String notification, Observer observer) {
Observable observable = observables.get(notification);
if (observable==null) {
observable = new Observable();
observables.put(notification, observable);
}
observable.addObserver(observer);
}
public void removeObserver(String notification, Observer observer) {
Observable observable = observables.get(notification);
if (observable!=null) {
observable.deleteObserver(observer);
}
}
public void postNotification(String notification, Object object) {
Observable observable = observables.get(notification);
if (observable!=null) {
observable.setChanged();
observable.notifyObservers(object);
}
}
}
Take a look at the Otto event bus from Square:
http://square.github.com/otto/
It has essentially the same features as NSNotificationCenter but thanks to annotations and static typing it is easier to follow the dependencies of components and paths that events follow. It's much simpler to use than the stock Android broadcast API, IMO.
i had the same wondrings.. so i wrote this:
public class NotificationCenter {
//static reference for singleton
private static NotificationCenter _instance;
private HashMap<String, ArrayList<Runnable>> registredObjects;
//default c'tor for singleton
private NotificationCenter(){
registredObjects = new HashMap<String, ArrayList<Runnable>>();
}
//returning the reference
public static synchronized NotificationCenter defaultCenter(){
if(_instance == null)
_instance = new NotificationCenter();
return _instance;
}
public synchronized void addFucntionForNotification(String notificationName, Runnable r){
ArrayList<Runnable> list = registredObjects.get(notificationName);
if(list == null) {
list = new ArrayList<Runnable>();
registredObjects.put(notificationName, list);
}
list.add(r);
}
public synchronized void removeFucntionForNotification(String notificationName, Runnable r){
ArrayList<Runnable> list = registredObjects.get(notificationName);
if(list != null) {
list.remove(r);
}
}
public synchronized void postNotification(String notificationName){
ArrayList<Runnable> list = registredObjects.get(notificationName);
if(list != null) {
for(Runnable r: list)
r.run();
}
}
}
and a usage for this will be:
NotificationCenter.defaultCenter().addFucntionForNotification("buttonClick", new Runnable() {
#Override
public void run() {
Toast.makeText(MainActivity.this, "Hello There", Toast.LENGTH_LONG).show();
}
});
tried to make the interface as similar to IOS as possible, but simpler (no object registration needed).
hope that helps:)
If you don't want to use Observer - it can be problematic in cases you want a Fragment to be your Observer cause you can't extend more then one class-
You can use google's Guava Library (https://code.google.com/p/guava-libraries/)
for "Function" and "Multimap" - although you can use as well HashMap> for the subscibersCollection
and implement something like this:
import java.util.Collection;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.base.Function;
public class EventService {
ArrayListMultimap<String, Function<Object, Void>> subscibersCollection;
private static EventService instance = null;
private static final Object locker = new Object();
private EventService() {
subscibersCollection = ArrayListMultimap.create();
}
public static EventService getInstance() {
if (instance == null) {
synchronized (locker) {
if (instance == null) {
instance = new EventService();
}
}
}
return instance;
}
/**
* Subscribe to the notification, and provide the callback functions in case
* notification is raised.
*
* #param notification
* - notification name
* #param func
* - function to apply when notification is raised
*/
public void addSubscription(String notification, Function<Object, Void> func) {
synchronized (subscibersCollection) {
if (!subscibersCollection.containsEntry(notification, func)) {
subscibersCollection.put(notification, func);
}
}
}
/**
* Remove subscription from notification
*/
public void removeSubscription(String notification,
Function<Object, Void> func) {
synchronized (subscibersCollection) {
subscibersCollection.remove(notification, func);
}
}
/**
* raise notification for all its subscribers
*
* #param notification
* - notification name
* #param data
* - update data
*/
public void publish(String notification, Object data) {
Collection<Function<Object, Void>> observableList = subscibersCollection
.get(notification);
for (Function<Object, Void> func : observableList) {
func.apply(data);
}
}
}
On the basis of Behlül answer, I change the code to make it closer to iOS NSNotificationCenter.
Another thing: the notifications will fire on the main thread
package com.oxygen.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.os.Handler;
public class NotificationCenter {
//---------------- event type list ---------------------
public static enum NotificationID{
IMAGES_CACHE_READY
}
//---------------- singelton ---------------------------
private static NotificationCenter instance = null;
private NotificationCenter() { observables = new HashMap<NotificationID, MyObservable>(); }
public static synchronized NotificationCenter singelton() {
if (instance == null) {
instance = new NotificationCenter ();
}
return instance;
}
//-------------------------------------------
public class Notification {
private Object poster; // the object that post the event
private Object info; // event specific data
private NotificationID id; // event name
public Notification(Object poster, NotificationID id, Object info) {
super();
this.poster = poster;
this.info = info;
this.id = id;
}
public Object getPoster() {
return poster;
}
public Object getInfo() {
return info;
}
public NotificationID getId() {
return id;
}
}
//-------------------------------------------
public interface Notifiable {
public void onNotification(Notification notify);
}
//-------------------------------------------
protected class MyObservable {
List<Notifiable> observers = new ArrayList<Notifiable>();
public MyObservable() {
}
public void addObserver(Notifiable observer) {
if (observer == null) {
throw new NullPointerException("observer == null");
}
synchronized (this) {
if (!observers.contains(observer))
observers.add(observer);
}
}
public int countObservers() {
return observers.size();
}
public synchronized void deleteObserver(Notifiable observer) {
observers.remove(observer);
}
public synchronized void deleteObservers() {
observers.clear();
}
public void notifyObservers(Notification notify) {
int size = 0;
Notifiable[] arrays = null;
synchronized (this) {
size = observers.size();
arrays = new Notifiable[size];
observers.toArray(arrays);
}
if (arrays != null) {
for (Notifiable observer : arrays) {
observer.onNotification(notify);
}
}
}
}
//-------------------------------------------
HashMap<NotificationID, MyObservable > observables;
public void addObserver(NotificationID id, Notifiable observer) {
MyObservable observable = observables.get(id);
if (observable==null) {
observable = new MyObservable ();
observables.put(id, observable);
}
observable.addObserver(observer);
}
public void removeObserver(NotificationID id, Notifiable observer) {
MyObservable observable = observables.get(id);
if (observable!=null) {
observable.deleteObserver(observer);
}
}
public void removeObserver(Notifiable observer) {
for (MyObservable observable : observables.values()) {
if (observable!=null) {
observable.deleteObserver(observer);
}
}
}
public void postNotification(final Object notificationPoster, final NotificationID id, final Object notificationInfo) {
final MyObservable observable = observables.get(id);
if (observable!=null) {
// notification post to the maim (UI) thread
// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(AppContext.get().getMainLooper());
Runnable myRunnable = new Runnable() {
#Override
public void run() {
observable.notifyObservers(new Notification(notificationPoster, id, notificationInfo) );
}
};
mainHandler.post(myRunnable);
}
}
}
Listener sample:
public class CustomGridViewAdapter extends ArrayAdapter<Category> implements Notifiable {
int layoutResourceId;
public CustomGridViewAdapter(Context context, int layoutResourceId) {
super(context, layoutResourceId);
this.layoutResourceId = layoutResourceId;
loadCategories(false);
NotificationCenter.singelton().addObserver(NotificationID.IMAGES_CACHE_READY, this);
}
public void onDestroy() {
NotificationCenter.singelton().removeObserver(this);
}
#Override
public void onNotification(Notification notify) {
switch (notify.getId()) {
case IMAGES_CACHE_READY:
loadCategories(true);
break;
}
}
...
}

Categories

Resources