package com.florianmski.tracktoid.rx.observables;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
import timber.log.Timber;
// This class has the same goal as CursorLoader but made with RxJava
// -> Refresh automagically the data associated with the cursor when the db has been modified
// I'm quite new to rxJava and I'm pretty sure this class is awful but I couldn't come with another solution
// Basically here is what's wrong
// -> never calling onCompleted() (otherwise the contentObserver is not working)
// -> passing the observer in this class seems plain wrong, I'm pretty sure there is a more "Rx" way to do it
// TODO should try this with a subject
public abstract class CursorObservable<T> implements Observable.OnSubscribe<T>
{
private final ForceLoadContentObserver contentObserver;
protected Uri uri;
protected String[] projection;
protected String selection;
protected String[] selectionArgs;
protected String sortOrder;
protected Context context;
protected Cursor cursor;
protected Subscriber<? super T> subscriber;
protected abstract T toObject(Cursor cursor);
public CursorObservable(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, boolean listenToUpdates)
{
this.contentObserver = new ForceLoadContentObserver();
this.context = context.getApplicationContext();
this.uri = uri;
this.projection = projection;
this.selection = selection;
this.selectionArgs = selectionArgs;
this.sortOrder = sortOrder;
if(listenToUpdates)
context.getContentResolver().registerContentObserver(uri, false, contentObserver);
}
public CursorObservable(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
this(context, uri, projection, selection, selectionArgs, sortOrder, true);
}
private void closeCursor()
{
if(cursor != null && !cursor.isClosed())
cursor.close();
}
@Override
public void call(Subscriber<? super T> subscriber)
{
this.subscriber = subscriber;
try
{
closeCursor();
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
if (cursor != null)
{
// Ensure the cursor window is filled
cursor.getCount();
T data = null;
if(cursor.moveToFirst())
data = toObject(cursor);
subscriber.onNext(data);
}
}
catch (Exception e)
{
e.printStackTrace();
subscriber.onError(e);
}
subscriber.add(Subscriptions.create(new Action0()
{
@Override
public void call()
{
closeCursor();
context.getContentResolver().unregisterContentObserver(contentObserver);
}
}));
}
public final class ForceLoadContentObserver extends ContentObserver
{
public ForceLoadContentObserver()
{
super(new Handler());
}
@Override
public boolean deliverSelfNotifications()
{
return true;
}
@Override
public void onChange(boolean selfChange)
{
onChange(selfChange, null);
}
@Override
public void onChange(boolean selfChange, Uri uri)
{
// update cursor only if it the same uri (because framework will trigger a onChange for the detailed uri
// and then all its descendants
// if uri is null, it means we are < API 16, so we don't know about the uri, update anyway
if(uri == null || uri.equals(CursorObservable.this.uri))
{
if(uri == null)
Timber.d("updating : null");
else
Timber.d("updating : " + uri.toString().replace("content://com.florianmski.tracktoid.data.provider.TraktoidProvider/", ""));
if(subscriber != null)
call(subscriber);
}
}
}
}