I have a question regarding MVP architecture, this is not a technical question.
I need to implement a chat (like facebook back and fourth chat) in my Android app. My question is how do I fit this to MVP.
I have these (simplified) classes:
public class ChatFragment extends Fragment {
private final ChatFragmentPresenter presenter;
#Override
public void onResume() {
super.onResume();
List<ChatMessage> chatHistory = repository.load(); //dont think fragment should actually interact with repository
displayChatHistory(chatHistory);
}
private void displayChatHistory(List<ChatMessage> chatHistory) {
//displays chat history..
}
}
public class ChatFragmentPresenter {
private final ChatFragment fragment;
}
public class ChatClient {
public interface ChatClientCallback {
void onMessageReceived();
void onMessageSentConfirmed();
}
public void start(ChatClientCallback callback) {
//starts the chat..
}
public void send(String message) {
//sends chat message..
//if successfully sent then onMessageSentConfirmed() is called
}
}
public class ChatRepository {
public interface LoadChatCallback {
List<ChatMessage> onLoadChatSuccess();
void onLoadChatFailed();
}
public void load(GetChatCallback callback) {
// loads saved chat messages..
}
public void save(ChatMessage message) {
//saves chat message..
}
public void clear() {
//deletes all saved chat messages..
}
}
public class ChatMessage {
public ChatMessage(String text, Type type) {
this.text = text;
this.type = type;
}
public final String text;
public final Type type;
public enum Type {FROM_OTHER, FROM_USER}
}
So my questions are:
Who is going to start the chat by calling ChatClient#start(callback)?
Who is responsible for receiving messages from ChatClient and sending messages to ChatClient?
Who is responsible for saving received chat messages to the repository?
Who is responsible for loading chat history from repository during onResume()?
Should the presenter do all of this? I would prefer if there was some kind of data layer that handled all retrieving / saving / getting / setting up and closing the ChatClient, and give presenter just the data in usable form
Who is going to start the chat by calling ChatClient#start(callback)?
-- it is the Application / Service / syncadapter
Who is responsible for receiving messages from ChatClient and sending messages to ChatClient? -- you can use application / service / syncadapter
Who is responsible for saving received chat messages to the repository? the application can send it to the content provider (content provider provide safe mechanism to access sqlite)
Who is responsible for loading chat history from repository during onResume()? the flow is Activity / Fragment -> Presenter -> Repository -> Data Provider
Related
I have a Xamarin.Android app with several activities and fragments. The app uses SignalR, connected with a .net core backend web app. There are several activities that may require visual modifications depending on the events called by the server. Is there any kind of in-app events that activities may subscribe to on creation that handles those required visual changes?
For example:
I am on an activity that shows 5 images related to a publication, and then the server sends a notification that the publication has been edited so the images have changed. In this case i would want that the SignalR client triggered some in-app event that updates the changed images on created activities of this kind.
I have came up with some kind of solution. I created a class called EventSubscriber that acts exactly how i wanted to.
public class EventSubscriber<T> : IEventSubscriber
{
#region Private members
private List<Tuple<T, Func<T, bool>>> Subscribtions { get; set; }
private ConcurrentDictionary<int, Func<T, bool>> WaitingObjectSubscribtions { get; set; }
#endregion
public EventSubscriber()
{
Subscribtions = new List<Tuple<T, Func<T, bool>>>();
WaitingObjectSubscribtions = new ConcurrentDictionary<int, Func<T, bool>>();
}
#region Subscribe
public void Subscribe(T adapter, Func<T, bool> function)
{
lock (Subscribtions)
{
Subscribtions.Add(new Tuple<T, Func<T, bool>>(adapter, function));
}
}
public int Subscribe(Func<T, bool> function)
{
lock (WaitingObjectSubscribtions)
{
int id = WaitingObjectSubscribtions.Count;
WaitingObjectSubscribtions.TryAdd(id, function);
return id;
}
}
#endregion
#region Unsubscribe
public void UnSubscribe(Tuple<T, Func<T, bool>> item)
{
lock (Subscribtions)
{
Subscribtions.Remove(item);
}
}
public void UnSubscribe(int id)
{
lock (WaitingObjectSubscribtions)
{
Func<T, bool> func;
WaitingObjectSubscribtions.TryRemove(id, out func);
}
}
public void UnSubscribeAll()
{
lock (Subscribtions)
{
Subscribtions.Clear();
}
WaitingObjectSubscribtions.Clear();
}
#endregion
#region Call Subscribed
public void CallSubscribed()
{
lock (Subscribtions)
{
foreach (var item in Subscribtions)
{
(var adapter, var function) = item;
if (!function(adapter))
{
Log.Debug("[EventSubscriber]", "Failed to notify adapter, will be automatically unsubscribed from this event");
UnSubscribe(item);
}
}
}
}
public void CallSubscribedWith(T adapter)
{
foreach (var id in WaitingObjectSubscribtions.Keys)
{
var function = WaitingObjectSubscribtions[id];
try
{
if (!function(adapter))
{
Log.Debug("[EventSubscriber]", "Failed to execute function");
UnSubscribe(id);
}
}
catch (Exception)
{
Log.Debug("[EventSubscriber]", "Failed to execute function");
UnSubscribe(id);
}
}
}
#endregion
}
To manage several EventSubscribed used in-app i created a static class Accessible to every Activity or Fragment which contains all the needed events:
public static class EventBoard
{
#region Products and Favorites
public static EventSubscriber<SwipeRefreshLayout> FinishedLoadingProducts = new EventSubscriber<SwipeRefreshLayout>();
public static EventSubscriber<List<Product>> SuccessfullyLoadedProducts = new EventSubscriber<List<Product>>();
public static EventSubscriber<RecyclerView.Adapter> UnsuccessfullyLoadedProducts = new EventSubscriber<RecyclerView.Adapter>();
//Search
public static EventSubscriber<SwipeRefreshLayout> FinishedLoadingProductsForSearch = new EventSubscriber<SwipeRefreshLayout>();
public static EventSubscriber<RecyclerView.Adapter> SuccessfullyLoadedProductsForSearch = new EventSubscriber<RecyclerView.Adapter>();
public static EventSubscriber<RecyclerView.Adapter> UnsuccessfullyLoadedProductsForSearch = new EventSubscriber<RecyclerView.Adapter>();
public static EventSubscriber<RecyclerView.Adapter> SearchNotFound = new EventSubscriber<RecyclerView.Adapter>();
//favorites
public static EventSubscriber<LocalProduct> ConfirmNewFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<LocalProduct> ConfirmRemoveFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<LocalProduct> NewFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<LocalProduct> RemoveFavoriteEvent = new EventSubscriber<LocalProduct>();
public static EventSubscriber<List<LocalProduct>> SetFavoritesEvent = new EventSubscriber<List<LocalProduct>>();
#endregion
}
So basically if a new activity is created it can subscribe to an EventSubscriber of its preference. Moreover, it can provide a function that receives any kind of object so it can be as flexible as it can.
The only thing that raise my concern, be sure you unsubscribe your events when the activity or the fragment View is destroyed because they can be a good source of memory leaks.
To communicate with the real-time server, you would have to use methods from the server's package. From researching, you can use the HubConnection.On method. Signalr registers the send and receive methods when you built the hub:
public class MyHub : Hub{
public async Task Send(string user, string message){
// Receive is the name of the listener method
await Clients.All.SendAsync("Receive", user, message);
}
}
Then, in your Android activities, you can register to the HubConnection (assuming you've sent the message about the edited image) of your listener method using the On method:
myHubConnection.On<string, string>("Receive", (user, message) =>
{
/* change UI here */
});
The way I'm getting callbacks from network requests is via interfaces.
Suppose there are two classes, A & B. Class A initiates all network requests which are performed by B. When B finishes the task, it has to respond to A.
The way I do it is:
public interface MyCallback {
void onTaskDone(String successMessage);
void onTaskFailed(String failMessage);
}
public class A {
onCreate() {
B objectB = new B();
objectB.loginUser(username, password, new MyCallback {
void onTaskDone(successmessage) {
//this is called when task is successful
}
void onTaskFailed(failMessage) {
//this is called when task is failed
});
}
}
}
public class B {
public void loginUser(String username, String password, MyCallback callback) {
//after task is performed
if (task.isSuccessful()) {
callback.onTaskDone("Successful");
} else {
callback.onTaskFailed("Programming is fun they said...");
}
}
}
As you can see, if a task is successful the interface methods are called from B which is received in A.
What my question is: Are there better ways to get callbacks besides using interfaces, or can this technique be made better? One issue I face while implementing this technique is, say I'm using same interface with many methods. In a particular case only one or two methods are used, while the rest remain unused, e,g. class B may never call onTaskFailed(). Is it normal that some methods are completely unused?
Android has a very good third party library like EventBus
https://github.com/greenrobot/EventBus
You can see its documentation, very easy to use.
public class A{
onCreate(){
B objectB = new B();
objectB.loginUser(username,password); //no need to pass callback
#Subscribe(threadMode = ThreadMode.MAIN)
public void onSuccessEvent(SuccessEvent successEvent) {
//this is called when task is successful
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onErrorEvent(ErrorEventsuccessEvent) {
//this is called when task is failed
}
}
public class B{
public void loginUser(String username, String password){
//after task is performed
if(task.isSuccessful()){
EventBus.getDefault().post(new SuccessEvent("Successful"));
}else{
EventBus.getDefault().post(new ErrorEvent("Programming is fun they said..."));
}
}
Your event classes
public class SuccessEvent {
private String message;
public SuccessEvent(String message) {
this.message=message;
}
}
public class ErrorEvent {
private String message;
public ErrorEvent(String message) {
this.message=message;
}
}
I found the answer to the question at the bottom: i.e the interface methods going unused.
To solve that I used abstract class! i.e an abstract class will implement all the interface callbacks.
Then while passing callback, simply pass the abstract class instance instead of the interface. This way, only the *required method can be overridden for getting the result.
How to update the RecyclerView Dataset from the background service.
The service maintains a socket connection with the server and when the server responds with data, the service has to update that in the recyclerview (that is in the MainActivity).
There is many way to send event from Serivce to Activity.
I recommend you the following way.
Bind and Callbacks
I think that Bind and Callbacks is official way.
Communication between Activity and Service
Example: Communication between Activity and Service using Messaging
EventBus
I think that EventBus is easy way.
https://github.com/greenrobot/EventBus
In Activity (or any where) :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
#Override
protected void onResume() {
super.onResume();
BusHolder.getInstnace().register(this);
}
#Override
protected void onPause() {
super.onPause();
BusHolder.getInstnace().unregister(this);
}
#Subscribe
public void onDatasetUpdated(DataSetUpdatedEvent event) {
//Update RecyclerView
}
}
BusHolder holds BusEvent instance:
public class BusHolder {
private static EventBus eventBus;
public static EventBus getInstnace() {
if (eventBus == null) {
eventBus = new EventBus();
}
return eventBus;
}
private BusHolder() {
}
}
The event posted:
public class DataSetUpdatedEvent {
//It is better to use database and share the key of record of database.
//But for simplicity, I share the dataset directly.
List<Data> dataset;
public DataSetUpdatedEvent(List<Data> dataset) {
this.dataset = dataset;
}
}
Send message from your Service.
BusHolder.getInstnace().post(new DataSetUpdatedEvent(dataset));
I hope this helps.
May be you should use some database like thing to store temporary data because I don't think it's a good thing to store data in an object on behalf of service component. It would be redundant to store whole list data into object as whether user comes back to app or not your object is going to cover memory which we should avoid throughout the development process. Best of luck.
I'm start learning RxJava and I like it so far. I have a fragment that communicate with an activity on button click (to replace the current fragment with a new fragment). Google recommends interface for fragments to communicate up to the activity but it's too verbose, I tried to use broadcast receiver which works generally but it had drawbacks.
Since I'm learning RxJava I wonder if it's a good option to communicate from fragments to activities (or fragment to fragment)?. If so, whats the best way to use RxJava for this type of communication?. Do I need to make event bus like this one and if that's the case should I make a single instance of the bus and use it globally (with subjects)?
Yes and it's pretty amazing after you learn how to do it. Consider the following singleton class:
public class UsernameModel {
private static UsernameModel instance;
private PublishSubject<String> subject = PublishSubject.create();
public static UsernameModel instanceOf() {
if (instance == null) {
instance = new UsernameModel();
}
return instance;
}
/**
* Pass a String down to event listeners.
*/
public void setString(String string) {
subject.onNext(string);
}
/**
* Subscribe to this Observable. On event, do something e.g. replace a fragment
*/
public Observable<String> getStringObservable() {
return subject;
}
}
In your Activity be ready to receive events (e.g. have it in the onCreate):
UsernameModel usernameModel = UsernameModel.instanceOf();
//be sure to unsubscribe somewhere when activity is "dying" e.g. onDestroy
subscription = usernameModel.getStringObservable()
.subscribe(s -> {
// Do on new string event e.g. replace fragment here
}, throwable -> {
// Normally no error will happen here based on this example.
});
In you Fragment pass down the event when it occurs:
UsernameModel.instanceOf().setString("Nick");
Your activity then will do something.
Tip 1: Change the String with any object type you like.
Tip 2: It works also great if you have Dependency injection.
Update:
I wrote a more lengthy article
Currently I think my preferred approach to this question is this to:
1.) Instead of one global bus that handles everything throughout the app (and consequently gets quite unwieldy) use "local" buses for clearly defined purposes and only plug them in where you need them.
For example you might have:
One bus for sending data between your Activitys and your ApiService.
One bus for communicating between several Fragments in an Activity.
One bus that sends the currently selected app theme color to all Activitys so that they can tint all icons accordingly.
2.) Use Dagger (or maybe AndroidAnnotations if you prefer that) to make the wiring-everything-together a bit less painful (and to also avoid lots of static instances). This also makes it easier to, e. g. have a single component that deals only with storing and reading the login status in the SharedPreferences - this component could then also be wired directly to your ApiService to provide the session token for all requests.
3.) Feel free to use Subjects internally but "cast" them to Observable before handing them out to the public by calling return subject.asObservable(). This prevents other classes from pushing values into the Subject where they shouldn't be allowed to.
Define events
public class Trigger {
public Trigger() {
}
public static class Increment {
}
public static class Decrement {
}
public static class Reset {
}
}
Event controller
public class RxTrigger {
private PublishSubject<Object> mRxTrigger = PublishSubject.create();
public RxTrigger() {
// required
}
public void send(Object o) {
mRxTrigger.onNext(o);
}
public Observable<Object> toObservable() {
return mRxTrigger;
}
// check for available events
public boolean hasObservers() {
return mRxTrigger.hasObservers();
}
}
Application.class
public class App extends Application {
private RxTrigger rxTrigger;
public App getApp() {
return (App) getApplicationContext();
}
#Override
public void onCreate() {
super.onCreate();
rxTrigger = new RxTrigger();
}
public RxTrigger reactiveTrigger() {
return rxTrigger;
}
}
Register event listener wherever required
MyApplication mApp = (App) getApplicationContext();
mApp
.reactiveTrigger() // singleton object of trigger
.toObservable()
.subscribeOn(Schedulers.io()) // push to io thread
.observeOn(AndroidSchedulers.mainThread()) // listen calls on main thread
.subscribe(object -> { //receive events here
if (object instanceof Trigger.Increment) {
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) + 1));
} else if (object instanceof Trigger.Decrement) {
if (Integer.parseInt(fabCounter.getText().toString()) != 0)
fabCounter.setText(String.valueOf(Integer.parseInt(fabCounter.getText().toString()) - 1));
} else if (object instanceof Trigger.Reset) {
fabCounter.setText("0");
}
});
Send/Fire event
MyApplication mApp = (App) getApplicationContext();
//increment
mApp
.reactiveTrigger()
.send(new Trigger.Increment());
//decrement
mApp
.reactiveTrigger()
.send(new Trigger.Decrement());
Full implementation for above library with example -> RxTrigger
I have an android application that is connected to the computer via USB cable. I use a TCPServer Class to send messages and listen. For example:
When I send a message like: request:x
I get the response: response:x:55
I need to make changes on my activity according to the response I get. At the moment I temporarily solved the problem by passing activity and activity class object to the TCPServer's constructor
public TCPServer(int portNum, Activity activity, IntroActivity ia) {
super();
port = portNum;
this.activity = activity;
this.ia = ia;
}
Then after I receive the response:
void updateButton(final int color, final String txt) {
activity.runOnUiThread(new Runnable() {
public void run() {
ia.getConnectionButton().setBackgroundColor(color);
ia.getConnectionButton().setText(txt);
}
});
}
As you see, this is not effective at all. I need to somehow notify the activity whenever a relevant variable is received. I use a Class for GlobalVariables and change those static variables after listen(), however I am having troubles notifying the activity.
First of all, it is almost always bad practice to pass Activity instances around. This is a time when it's bad.
Define an interface and use a callback to let the activity know that a response has been received.
public interface ResponseReceivedListener {
void onResponseReceived(int arg1, string arg2); // <- add arguments you want to pass back
}
In your TCPServer class:
ArrayList<ResponseReceivedListener> listeners = new ArrayList<>();
// ...
public void setResponseReceivedListener(ResponseReceivedListener listener) {
if (!listeners.contains(listener) {
listeners.add(listener);
}
}
public void removeResponseReceivedListener(ResponseReceivedListener listener) {
if (listeners.contains(listener) {
listeners.remove(listener);
}
}
When you receive a response:
for (ResponseReceivedListener listener : listeners) {
listener.onResponseReceived(arg1, arg2);
}
In your Activity:
public class MainActivity extends Activity implements ResponseReceivedListener {
// ...
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
tcpServer.setResponseReceivedListener(this);
// ...
}
public void onResponseReceived(int arg1, string arg2) {
// do whatever you need to do
}
// ...
}
All from memory so please excuse typos.
This approach decouples the classes. The TCP Server has no knowledge of the activities. It simply calls back to any listeners registered. Those listeners might be Activities, they might be services. They might be instances of MySparklyUnicorn. The server neither knows nor cares. It simply says "if anyone's interested, I've received a response and here are the details".