I am at my last year at the university and working on my final project with a group of friends.
I am responsible on implementing the database (using google firestore in java) and i am trying to implement it using a design pattern.
I found the adapter quiet useful, as I can create an interface called:
GenericDB, which contains all the methods the database needs to use.
A concrete class, let's call her FirestoreDB which implements it,
and an Adapter, which also implements the GenericDB, and holds an Instance Of GenericDB as a variable, so I can choose at run time which db I will want to use (maybe in the future the db would change)
Here is some basic code:
public interface GenericDB {
boolean add(String... args);
boolean delete(String... args);
boolean get(String... args);
boolean changePassword(String... args);
}
public class FirestoreDB implements GenericDB {
private final Firestore db;
public FirestoreDB() {
FirestoreOptions firestoreOptions =
FirestoreOptions.getDefaultInstance().toBuilder()
.setProjectId(Constants.PROJECT_ID)
.build();
this.db = firestoreOptions.getService();
}
public boolean add(String... args) {
return true;
}
public boolean delete(String... args) {
return false;
}
public boolean get(String... args) {
return false;
}
public boolean changePassword(String... args) {
return false;
}
}
public class Adapter implements GenericDB {
private GenericDB db;
public Adapter(GenericDB db){
this.db = db;
}
public boolean add(String... args) {
return this.db.add(args);
}
public boolean delete(String... args) {
return db.delete(args);
}
public boolean get(String... args) {
return db.get(args);
}
public boolean changePassword(String... args) {
return db.changePassword(args);
}
}
public class DatabaseCreator {
public GenericDB getDB(DATABASE database) {
switch (database) {
case FIRESTORE:
return new FirestoreDB();
default:
return null;
}
}
DatabaseCreator database = new DatabaseCreator();
GenericDB db = database.getDB(EXTRA.DATABASE.FIRESTORE);
Adapter ad = new Adapter(db);
System.out.println(ad.add("1"));
Is this a good use of the adapter pattern?
Is this a good use of the adapter pattern?
What you call Adapter, is not an Adapter. Purpose of Adapter is to convert interface of the class to another interface expected by the client. But your 'adapter' implements the same GenericDB interface as the adaptee it wraps - no conversion happens here.
The closest one to Adapter is your FirestoreDB class. It is intended to convert the interface of Firestore to the interface GenericDB required by your code. Of course there is still small chance that Google will change Firestore to implement your GenericDB interface. Until then you should use an adapter.
To make FirestoreDB a proper adapter, you should pass adaptee (Firestore object) to the constructor of adapter, and later call it in the GenericDB methods implementation:
public class FirestoreAdapter implements GenericDB {
private final Firestore db;
public FirestoreAdapter(Firestore db) { // pass adaptee to adapter
this.db = db;
}
public boolean add(...) {
// DocumentReference docRef = db.collection(colName).document(docId);
// etc
}
// etc
}
This adapter could be passed to the code which expects GenericDB interface and knows nothing about Firestore interface:
FirestoreOptions firestoreOptions =
FirestoreOptions.getDefaultInstance().toBuilder()
.setProjectId(Constants.PROJECT_ID)
.build();
Firestore firestore = firestoreOptions.getService(); // but your code requires GenericDB
GenericDB db = new FirestoreAdapter(firestore); // adapt Firestore interface
// use db here as if Firestore was implementing your GenericDB interface
You can write adapters for another type of database in the same way. But... usually, you don't use some abstract database interface, because databases are very different with different sets of features. Trying to find some intersection of features supported by all databases might be not a great idea (unless you writing CosmosDB). Usually, you will work with higher-level abstractions, like Repositories.
You should use Command Design Pattern it more flexible than Adapter in your case
Example:
import java.util.HashMap;
import java.util.Map;
// demo for firestore
class Firestore {
private Map<Object, Object> map = new HashMap<>();
public void add(Object id, Object object) {
map.put(id, object);
}
public Object get(Object id) {
return map.get(id);
}
}
interface FirestoreAware {
void setFirestore(Firestore firestore);
}
enum CommandType {
ADD,
DELETE,
GET,
CHANGE_PASSWORD,
GET_USER
}
interface Command {
CommandType getType();
}
class GetCommand implements Command {
private int id;
public GetCommand id(int id) {
this.id = id;
return this;
}
public int getId() {
return id;
}
#Override
public CommandType getType() {
return CommandType.GET;
}
}
class AddCommand implements Command {
private int id;
private String jsonData;
public AddCommand id(int id) {
this.id = id;
return this;
}
public AddCommand jsonData(String jsonData) {
this.jsonData = jsonData;
return this;
}
public int getId() {
return id;
}
public String getJsonData() {
return jsonData;
}
#Override
public CommandType getType() {
return CommandType.ADD;
}
}
interface CommandHandler<C> {
Object handle(C cmd);
}
abstract class CommandFirestoreHandler<C>
implements CommandHandler<C>, FirestoreAware {
protected Firestore firestore;
#Override
public void setFirestore(Firestore firestore) {
this.firestore = firestore;
}
}
class AddCommandHandler extends CommandFirestoreHandler<AddCommand> {
#Override
public Object handle(AddCommand cmd) {
firestore.add(cmd.getId(), cmd.getJsonData());
return Boolean.TRUE;
}
}
class GetCommandHandler extends CommandFirestoreHandler<GetCommand> {
#Override
public Object handle(GetCommand cmd) {
return firestore.get(cmd.getId());
}
}
interface GenericDB {
<T> T execute(Command cmd);
}
class FirestoreDB implements GenericDB {
private final Firestore firestore;
private final Map<CommandType, CommandHandler> handlers;
public FirestoreDB() {
this(new Firestore());
}
public FirestoreDB(Firestore firestore) {
this.firestore = firestore;
this.handlers = new HashMap<>();
// demo add default command handlers
this.addHandler(CommandType.ADD, new AddCommandHandler());
this.addHandler(CommandType.GET, new GetCommandHandler());
}
public void addHandler(CommandType commandType, CommandHandler handler) {
if(handler instanceof FirestoreAware)
((FirestoreAware)handler).setFirestore(firestore);
this.handlers.put(commandType, handler);
}
#Override
public <T> T execute(Command cmd) {
CommandHandler handler = handlers.get(cmd.getType());
return (T)handler.handle(cmd);
}
}
class DatabaseCreator {
public GenericDB getDB(String database) {
switch (database) {
case "FIRESTORE":
return new FirestoreDB();
default:
return null;
}
}
}
public class GenericDBDemo {
public static void main(String[] args) {
DatabaseCreator database = new DatabaseCreator();
GenericDB db = database.getDB("FIRESTORE");
db.execute(new AddCommand().id(1).jsonData("{'_id': 1, 'name' : 'hello world'}"));
System.out.println(db.execute(new GetCommand().id(1)).toString());
}
}
Related
I was wondering how can I return a concrete implementation given a variable as argument in a function.
This is my test code
public interface Items {
String getName();
}
public class Car implements Items{
#Override
public String getName() {
return "Car";
}
public void drive(){
//To something
}
}
public class Shelf implements Items{
#Override
public String getName() {
return "Shelf";
}
public String getBooks(String bookName){
return bookName;
}
}
public enum Item {
CAR(Service::getCar),
TABLE(Service::getShelf),
;
Function<Service, ? extends Items> serviceFunction;
Item(Function<Service, ? extends Items> serviceFunction) {
this.serviceFunction = serviceFunction;
}
}
public class Service {
public Car getCar(){
return new Car();
}
public Shelf getShelf(){
return new Shelf();
}
public Items getItem(Item item){
return item.serviceFunction.apply(this);
}
public static void main(String[] args) {
Service service = new Service();
service.getItem(Item.CAR).getName();
// service.getItem(Item.CAR).drive(); // This is not valid.
}
}
So what I want is based on that enum I should be able to execute a set of functions related to that enum without passing the implementation identifier itself.
I know I can do this. And I will work but I was thinking of getting the concrete implementation without passing Class<T> klass.
public <T extends Items> T getItem(Item item, Class<T> klass){
return (T) item.serviceFunction.apply(this);
}
public static void main(String[] args) {
Service service = new Service();
service.getItem(Item.CAR, Car.class).drive();
}
How can DAO be used with realm? Because when in my activity I try to set members of my model class I get an exception :
java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.
I know that using realm.executeTransaction fixes the issue, but the code in my activity is no more database-agnostic because it will countain code that is specific to low level database communication. So later if I want to change database, the refactoring will cost a lot of time and work... Besides, I will have to handle in all my activities a reference to Realm.getDefaultInstance();
Here is sample of code of my activity
protected void onCreate(Bundle savedInstanceState)
{
mBook = mBookDaoImpl.getBookById(bookId);
}
// Later in the code
private void saveBook(String name)
{
mBook.setName(name);
}
Here is my model class
public class Book extends RealmObject
{
#Required
#PrimaryKey
private String id;
private String name;
public Book() {
}
public Book(String id, String name) {
this.id = id;
this.name = name;
}
// getter setter methods
}
Here is my DAO interface :
public interface BookDao
{
List<Book> getAllBooks();
Book getBookByIsbn(int isbn);
void saveBook(Book book);
void deleteBook(Book book);
}
And finally is my implementation :
public class BookDaoImpl implements BookDao
{
private static BookDaoImpl INSTANCE = null;
private Realm mRealm;
private BookDaoImpl()
{
mRealm = Realm.getDefaultInstance();
}
public static BookDaoImpl getInstance()
{
if (INSTANCE == null)
INSTANCE = new BookDaoImpl();
return INSTANCE;
}
#Override
public List<Book> getAllBooks()
{
return mRealm.where(Book.class).findAll();
}
#Override
public Book getBookById(String id)
{
return mRealm.where(Book.class).equalTo("id", id).findFirst();
}
#Override
public void saveBook(final Book book)
{
mRealm.executeTransaction(new Realm.Transaction()
{
#Override
public void execute(Realm realm)
{
if (book.getId() == null)
book.setId(UUID.randomUUID().toString());
realm.copyToRealmOrUpdate(book);
}
});
}
#Override
public void deleteBook(final Book book)
{
mRealm.executeTransaction(new Realm.Transaction()
{
#Override
public void execute(Realm realm)
{
mRealm.where(Counter.class).equalTo("id", book.getId())
.findFirst()
.deleteFromRealm();
}
});
}
}
Realm's getInstance() method returns a thread-local, reference counted instance which must be paired with a close() call, so your DAO implementation probably won't suit what you expect.
If you use my library Realm-Monarchy which I created specifically for making it easier to "abstract Realm away", then you can implement your DAO like this:
public class BookDaoImpl implements BookDao
{
private static BookDaoImpl INSTANCE = null;
private Monarchy monarchy;
private BookDaoImpl(Monarchy monarchy)
{
this.monarchy = monarchy;
}
public static BookDaoImpl getInstance(Monarchy monarchy)
{
if (INSTANCE == null) {
synchronized(BookDaoImpl.class) {
if(INSTANCE == null) {
INSTANCE = new BookDaoImpl(monarchy);
}
}
}
return INSTANCE;
}
#Override
public List<Book> getAllBooks()
{
return monarchy.fetchAllCopiedSync((realm) -> realm.where(Book.class));
}
#Override
public Book getBookById(final String id)
{
List<Book> books = monarchy.fetchAllCopiedSync((realm) -> realm.where(Book.class).equalTo("id", id));
if(books.isEmpty()) {
return null;
} else {
return books.get(0);
}
}
#Override
public void saveBook(final Book book)
{
monarchy.runTransactionSync((realm) -> {
if (book.getId() == null)
book.setId(UUID.randomUUID().toString());
realm.insertOrUpdate(book);
});
}
#Override
public void deleteBook(final Book book)
{
monarchy.runTransactionSync((realm) -> {
realm.where(Counter.class).equalTo("id", book.getId())
.findFirst()
.deleteFromRealm();
});
}
}
P.S.: you're throwing away a lot of power/functionality if you return List<T> synchronously, instead of an observable like LiveData<List<T>> (or originally, RealmResults<T>).
I have the following interface:
public interface ProvidersFilter {
void setQuery(#NonNull Object query);
Object apply();
}
And the following implementing classes:
First implementation:
public class ProvidersRemoteFilter implements ProvidersFilter {
private Query mQuery;
#Override
public void setQuery(#NonNull Object query) {
if (query instanceof Query) {
mQuery = (Query) query;
} else {
throw new RuntimeException("query object must be of type com.google.firebase.firestore.Query");
}
}
#Override
public Object apply() {
return mQuery;
}
}
Second implementation:
public class ProvidersLocalFilter implements ProvidersFilter {
private String mQuery;
#Override
public void setQuery(#NonNull Object query) {
if (query instanceof String) {
mQuery = (String) query;
} else {
throw new RuntimeException("query object must be of type String");
}
}
#Override
public Object apply() {
return mQuery;
}
}
I would like to avoid using instanceof by generifying my interface and implementing classes.
You need to add a type variable to your interface.
Interface:
public interface ProvidersFilter<T> {
void setQuery(#NonNull T query);
T apply();
}
Implementing class:
public class ProvidersRemoteFilter implements ProvidersFilter<Query> {
private Query mQuery;
#Override
public void setQuery(#NonNull Query query) {
mQuery = query;
}
#Override
public Query apply() {
return mQuery;
}
}
Since local and remote Providers have the same code you could have an abstract class that implements the commonalities. In that case there is no need for the interface but you can keep it all the same:
public interface ProvidersFilter<Q> {
void setQuery(#NonNull Q query);
Q apply();
}
abstract class AbstractProvidersFilter<Q> {
private Q mQuery;
#Override
public void setQuery(#NonNull Q query) {
mQuery = query;
}
#Override
public Q apply() {
return mQuery;
}
}
public class ProvidersRemoteFilter extends AbstractProvidersFilter<Query> {}
public class ProvidersLocalFilter extends AbstractProvidersFilter<String> {}
I am trying to access the method GetDatbaseName(), from the returned object obj, but it is returning error that the method is not available.
However, when I Typecast the obj, it is working.
String name = ((Oracle)obj).GetDatabaseName();
How to handle this generic? Like I can't typecast for each return type like Oracle and MongoDB. Also any better implementation for this?
// one class needs to have a main() method
public class HelloWorld
{
// arguments are passed using the text field below this editor
public static void main(String[] args)
{
Data dt = new Data("Oracle");
Object obj = dt.GetObject();
String name = obj.GetDatabaseName();
System.out.println(name);
}
}
public class Data
{
public String _type;
public Data(String type)
{
_type = type;
}
public Object GetObject()
{
Object obj = null;
switch(_type)
{
case("Oracle"):
obj = new Oracle("Test");
break;
case("MongoDB"):
obj = new MongoDB("TestCollection");
break;
}
return obj;
}
}
public class Oracle
{
public String _databaseName;
public Oracle(String databaseName)
{
_databaseName = databaseName;
}
public String GetDatabaseName() { return _databaseName; }
}
public class MongoDB
{
public String _collectionName;
public MongoDB(String collectionName)
{
_collectionName = collectionName;
}
public String GetCollectionName() { return _collectionName; }
}
There are two ways to solve this, the first is using a generic class, while the second is using interface, the second approach is better if you know that the classes will have the same methods, while the generic approach is if the classes have different methods
Generic approach
public class DBtest{
public static void main(String[] args){
DataBase<Oracle> database = new DataBase<>(Oracle.class);
Oracle oracle = database.getDataBase();
System.out.println(oracle.getDatabaseName());
}
}
class DataBase<T>{
private T database;
public DataBase(Class<T> classOfT){
try {
database = classOfT.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public T getDataBase(){
return database;
}
}
class Oracle{
private String _databaseName;
public Oracle(){
_databaseName = "test";
}
public String getDatabaseName() { return _databaseName; }
}
As you can see, it is not possible to define the name of the database, this would be possible of you write <T extends Name> which is an interface which has getName() and setName() method
Interface approach
public class DBtest{
// arguments are passed using the text field below this editor
public static void main(String[] args){
DataBase database = new DataBase(new Oracle("test"));
DatabaseName databaseName = database.getDataBase();
System.out.println(databaseName.getName());
}
}
interface DatabaseName {
String getName();
}
class DataBase{
private DatabaseName databaseName;
public DataBase(DatabaseName databaseName){
this.databaseName = databaseName;
}
public DatabaseName getDataBase(){
return databaseName;
}
}
class Oracle implements DatabaseName {
private String _databaseName;
public Oracle(String name){
_databaseName = name;
}
public String getName() {
return _databaseName;
}
}
class MongoDB implements DatabaseName {
private String _databaseName;
public MongoDB(String name){
_databaseName = name;
}
public String getName() {
return _databaseName;
}
}
Obviously DatabaseName is a bad name for an interface, but it is the only method which is the same for both classes, so it makes sense to call it that. The great thing about interfaces is that you don't have to give a shit about what class is used as long as you know the method names.
You problem is on the following lines:
Object obj = dt.GetObject();
String name = obj.GetDatabaseName();
As far as those lines are concerned, obj is of type Object, which does not have the invoked method; thus, the issue. This is due to Java being strongly typed.
To go around that, you need a type that has this method, or use reflection. To use a type that has this method, they need to inherit it from a common parent of implement it from a common interface. You can also wrap you objects or a bunch of other alternatives.
In your case, it seems that a common interface is the easiest way to go. In this case, each class should implement this interface and instead of using Object your reference would be of the type of that interface.
public Object GetObject()
Would become
public MyInterface GetObject()
and
public class Oracle
would be
public class Oracle implements MyInterface
Where MyInterface would declare the method
public interface MyInterface {
String GetDatabaseName();
}
Being mindful of Java conventions, methods should start with lowercase
public interface MyInterface {
String getDatabaseName();
}
In the case where you cannot change the code in order to implements those methods, you can use "instanceof" to test against the class type.
name = (obj instanceof Oracle)?((Oracle)obj).GetDatabaseName():((MongoDB )obj).getCollectionName();
You must have to create an Interface and then with getDatabaseName() method. Then your objects Oracle and MongoDB must implement that interface.
What you are trying to do is something similar to AbstractFactory Pattern. You should google it.
public interface MyDbInterface {
String getDatabaseName();
}
public class HelloWorld {
// arguments are passed using the text field below this editor
public static void main(String[] ){
MyDbInterface dt = DataFactory.create("Oracle");
String name = dt.getDatabaseName();
System.out.println(name);
}
}
public final class DataFactory{
private DataFactory(){
super();
}
public static MyDbInterface create(String type){
MyDbInterface obj = null;
switch(type) {
case("Oracle"):
obj = new Oracle("Test");
break;
case("MongoDB"):
obj = new MongoDB("TestCollection");
break;
}
return obj;
}
}
public class Oracle implement MyDbInterface{
public String databaseName;
public Oracle(String databaseName){
databaseName = databaseName;
}
#Override
public String getDatabaseName() {
return databaseName;
}
}
public class MongoDB implement MyDbInterface{
public String collectionName;
public MongoDB(String collectionName){
collectionName = collectionName;
}
public String getCollectionName() {
return collectionName;
}
#Override
public String getDatabaseName() {
return getCollectionName();
}
}
I suposed you come from C#, check java style guide. ;)
You should think about the design of your code. You need to use basic OOP principal to solve the problem. There are several ways to solve your problem like using interface/generics etc. Here I am giving one such example.
public class HelloWorld {
public static void main(String[] args) {
Data dt = new Data("Oracle");
DataBase obj = dt.GetObject();
String name = obj.getDatabaseName();
System.out.println("Name : "+name);
}
}
class Data {
public String _type;
public Data(String type) {
_type = type;
}
public DataBase GetObject() {
DataBase dataBase=null;
switch (_type) {
case "Oracle":
dataBase = new Oracle();
break;
case "Mongo":
dataBase = new MongoDb();
break;
}
return dataBase;
}
}
interface DataBase {
String getDatabaseName();
}
class Oracle implements DataBase {
public String getDatabaseName() {
return "Oracle";
}
}
class MongoDb implements DataBase {
public String getDatabaseName() {
return "Mongo";
}
}
Edited:
Here is another way to solve your problem. I believe this approach might solve your problem.
public class HelloWorld {
public static void main(String[] args) {
Data<Oracle> dt = new Data<Oracle>("Oracle");
Oracle obj = dt.getObject();
String name = obj.getDatabaseName();
System.out.println("Name : "+name);
}
}
class Data<T> {
public String _type;
public Data(String type) {
_type = type;
}
public T getObject() {
Object dataBase=null;
switch (_type) {
case "Oracle":
dataBase = new Oracle();
break;
case "Mongo":
dataBase = new MongoDb();
break;
}
return (T)dataBase;
}
}
class Oracle {
public String getDatabaseName() {
return "Oracle";
}
}
class MongoDb {
}
I'm using a multiplayer Game Client that's called AppWarp (http://appwarp.shephertz.com), where you can add event listeners to be called back when event's happen, let's assume we'll be talking about the Connection Listener, where you need to implement this interface:
public interface ConnectionRequestListener {
void onConnectDone(ConnectEvent var1);
void onDisconnectDone(ConnectEvent var1);
void onInitUDPDone(byte var1);
}
My goal here is to mainly create a Reactive version of this client to be used in my Apps Internally instead of using the Client itself directly (I'll also rely on interfaces later instead of just depending on the WarpClient itself as in the example, but that's not the important point, please read my question at the very end).
So what I did is as follows:
1) I introduced a new event, named it RxConnectionEvent (Which mainly groups Connection-Related events) as follows:
public class RxConnectionEvent {
// This is the original connection event from the source client
private final ConnectEvent connectEvent;
// this is to identify if it was Connection / Disconnection
private final int eventType;
public RxConnectionEvent(ConnectEvent connectEvent, int eventType) {
this.connectEvent = connectEvent;
this.eventType = eventType;
}
public ConnectEvent getConnectEvent() {
return connectEvent;
}
public int getEventType() {
return eventType;
}
}
2) Created some event types as follows:
public class RxEventType {
// Connection Events
public final static int CONNECTION_CONNECTED = 20;
public final static int CONNECTION_DISCONNECTED = 30;
}
3) Created the following observable which emits my new RxConnectionEvent
import com.shephertz.app42.gaming.multiplayer.client.WarpClient;
import com.shephertz.app42.gaming.multiplayer.client.events.ConnectEvent;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.subscriptions.Subscriptions;
public class ConnectionObservable extends BaseObservable<RxConnectionEvent> {
private ConnectionRequestListener connectionListener;
// This is going to be called from my ReactiveWarpClient (Factory) Later.
public static Observable<RxConnectionEvent> createConnectionListener(WarpClient warpClient) {
return Observable.create(new ConnectionObservable(warpClient));
}
private ConnectionObservable(WarpClient warpClient) {
super(warpClient);
}
#Override
public void call(final Subscriber<? super RxConnectionEvent> subscriber) {
subscriber.onStart();
connectionListener = new ConnectionRequestListener() {
#Override
public void onConnectDone(ConnectEvent connectEvent) {
super.onConnectDone(connectEvent);
callback(new RxConnectionEvent(connectEvent, RxEventType.CONNECTION_CONNECTED));
}
#Override
public void onDisconnectDone(ConnectEvent connectEvent) {
super.onDisconnectDone(connectEvent);
callback(new RxConnectionEvent(connectEvent, RxEventType.CONNECTION_DISCONNECTED));
}
// not interested in this method (for now)
#Override
public void onInitUDPDone(byte var1) { }
private void callback(RxConnectionEvent rxConnectionEvent)
{
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(rxConnectionEvent);
} else {
warpClient.removeConnectionRequestListener(connectionListener);
}
}
};
warpClient.addConnectionRequestListener(connectionListener);
subscriber.add(Subscriptions.create(new Action0() {
#Override
public void call() {
onUnsubscribed(warpClient);
}
}));
}
#Override
protected void onUnsubscribed(WarpClient warpClient) {
warpClient.removeConnectionRequestListener(connectionListener);
}
}
4) and finally my BaseObservable looks like the following:
public abstract class BaseObservable<T> implements Observable.OnSubscribe<T> {
protected WarpClient warpClient;
protected BaseObservable (WarpClient warpClient)
{
this.warpClient = warpClient;
}
#Override
public abstract void call(Subscriber<? super T> subscriber);
protected abstract void onUnsubscribed(WarpClient warpClient);
}
My question is mainly: is my implementation above correct or should I instead create separate observable for each event, but if so, this client has more than 40-50 events do I have to create separate observable for each event?
I also use the code above as follows (used it in a simple "non-final" integration test):
public void testConnectDisconnect() {
connectionSubscription = reactiveWarpClient.createOnConnectObservable(client)
.subscribe(new Action1<RxConnectionEvent>() {
#Override
public void call(RxConnectionEvent rxEvent) {
assertEquals(WarpResponseResultCode.SUCCESS, rxEvent.getConnectEvent().getResult());
if (rxEvent.getEventType() == RxEventType.CONNECTION_CONNECTED) {
connectionStatus = connectionStatus | 0b0001;
client.disconnect();
} else {
connectionStatus = connectionStatus | 0b0010;
connectionSubscription.unsubscribe();
haltExecution = true;
}
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
fail("Unexpected error: " + throwable.getMessage());
haltExecution = true;
}
});
client.connectWithUserName("test user");
waitForSomeTime();
assertEquals(0b0011, connectionStatus);
assertEquals(true, connectionSubscription.isUnsubscribed());
}
I suggest you avoid extending the BaseObservable directly since it's very error prone. Instead, try using the tools Rx itself gives you to create your observable.
The easiest solution is using a PublishSubject, which is both an Observable and a Subscriber. The listener simply needs to invoke the subject's onNext, and the subject will emit the event. Here's a simplified working example:
public class PublishSubjectWarpperDemo {
public interface ConnectionRequestListener {
void onConnectDone();
void onDisconnectDone();
void onInitUDPDone();
}
public static class RxConnectionEvent {
private int type;
public RxConnectionEvent(int type) {
this.type = type;
}
public int getType() {
return type;
}
public String toString() {
return "Event of Type " + type;
}
}
public static class SimpleCallbackWrapper {
private final PublishSubject<RxConnectionEvent> subject = PublishSubject.create();
public ConnectionRequestListener getListener() {
return new ConnectionRequestListener() {
#Override
public void onConnectDone() {
subject.onNext(new RxConnectionEvent(1));
}
#Override
public void onDisconnectDone() {
subject.onNext(new RxConnectionEvent(2));
}
#Override
public void onInitUDPDone() {
subject.onNext(new RxConnectionEvent(3));
}
};
}
public Observable<RxConnectionEvent> getObservable() {
return subject;
}
}
public static void main(String[] args) throws IOException {
SimpleCallbackWrapper myWrapper = new SimpleCallbackWrapper();
ConnectionRequestListener listner = myWrapper.getListener();// Get the listener and attach it to the game here.
myWrapper.getObservable().observeOn(Schedulers.newThread()).subscribe(event -> System.out.println(event));
listner.onConnectDone(); // Call the listener a few times, the observable should print the event
listner.onDisconnectDone();
listner.onInitUDPDone();
System.in.read(); // Wait for enter
}
}
A more complex solution would be to use one of the onSubscribe implementations to create an observable using Observable.create(). For example AsyncOnSubscibe. This solution has the benefit of handling backperssure properly, so your event subscriber doesn't become overwhelmed with events. But in your case, that sounds like an unlikely scenario, so the added complexity is probably not worth it.