I use a Notifications interface to update fragments whenever data is changed.
public interface Notifications {
void register(ID id, Listener listener);
void unregister(ID id, Listener listener);
<T> void post(ID id, T value);
interface Listener<T> {
void onEvent(ID id, T value);
}
enum ID {
CustomersUpdated,
ProductsUpdated
}
}
With regards to the Android Lifecycle, what is the best point to register and unregister for notifications?
Here are some scenarios:
Scenario 1:
public class ProductsListFragment extends BaseFragment
implements Notifications.Listener {
#Override
public void onStart() {
mAdapter.notifyDataChanged();
register(Notifications.ID.ProductsUpdated, this)
super.onStart();
}
#Override
public void onStop() {
unregister(Notifications.ID.ProductsUpdated, this)
super.onStop();
}
#Override
public void onEvent(Notifications.ID id, Object value) {
mAdapter.notifyDataChanged();
}
Scenario 2:
public class ProductsListFragment extends BaseFragment
implements Notifications.Listener {
#Override
public void onResume() {
mAdapter.notifyDataChanged();
register(Notifications.ID.ProductsUpdated, this)
super.onResume();
}
#Override
public void onPause() {
unregister(Notifications.ID.ProductsUpdated, this)
super.onPause();
}
#Override
public void onEvent(Notifications.ID id, Object value) {
mAdapter.notifyDataChanged();
}
Please explain why you would suggest using one or the other implementation .. or another!
There isn't a universal answer to this question. onResume/onPause will probably give the expected behaviour most of the time but you might run into cases where you want to do it earlier or later.
On a different note, though, two points on style and functionality - call super.onResume as the first thing in the method (and super.onStop as the last). That way your cycle is entirely nested inside the "super" cycle and you avoid weird bugs and edge cases. Further, it's not a great idea to always call notifyDataSetChanged in onResume. In fact, it's probably a pretty wasteful idea.
I would stick with Scenario 2. Although the order in which onPause() and onResume() is linear for fragments, the same is not true for Activities.
Since the fragments' pause and resume are called whenever the activity's is, broadcasts would be received whenever the activity is active. However, the activity does not call onStop() until it loses visibility. In this case, the fragments would still process broadcasts while the activity it is contained in is inactive, which doesn't sound like a very good idea to me.
Related
This question already has answers here:
How to make a Java class that implements one interface with two generic types?
(9 answers)
Closed 8 years ago.
I have the following interface, which I want to implement multiple times in my classes:
public interface EventListener<T extends Event>
{
public void onEvent(T event);
}
Now, I want to be able to implement this interface in the following way:
class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>
{
#Override
public void onEvent(LoginEvent event)
{
}
#Override
public void onEvent(LogoutEvent event)
{
}
}
However, this gives me the error: Duplicate class com.foo.EventListener on the line:
class Foo implements EventListener<LoginEvent>, EventListener<LogoutEvent>
Is it possible to implement the interface twice with different generics? If not, what's the next closest thing I can do to achieve what I'm trying to do here?
Is it possible to implement the interface twice with different generics
Unfortunately no. The reason you can't implement the same interface twice is because of type erasure. The compiler will handle type parameters, and a runtime EventListener<X> is just a EventListener
If not, what's the next closest thing I can do to achieve what I'm trying to do here?
Type erasure can work in our favor. Once you know that EventListener<X> and EventListener<Y> are just raw EventListener at run-time, it is easier than you think to write an EventListener that can deal with different kinds of Events. Bellow is a solution that passes the IS-A test for EventListener and correctly handles both Login and Logout events by means of simple delegation:
#SuppressWarnings("rawtypes")
public class Foo implements EventListener {
// Map delegation, but could be anything really
private final Map<Class<? extends Event>, EventListener> listeners;
// Concrete Listener for Login - could be anonymous
private class LoginListener implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
System.out.println("Login");
}
}
// Concrete Listener for Logout - could be anonymous
private class LogoutListener implements EventListener<LogoutEvent> {
public void onEvent(LogoutEvent event) {
System.out.println("Logout");
}
}
public Foo() {
#SuppressWarnings("rawtypes")
Map<Class<? extends Event>, EventListener> temp = new HashMap<>();
// LoginEvents will be routed to LoginListener
temp.put(LoginEvent.class, new LoginListener());
// LogoutEvents will be routed to LoginListener
temp.put(LogoutEvent.class, new LogoutListener());
listeners = Collections.unmodifiableMap(temp);
}
#SuppressWarnings("unchecked")
#Override
public void onEvent(Event event) {
// Maps make it easy to delegate, but again, this could be anything
if (listeners.containsKey(event.getClass())) {
listeners.get(event.getClass()).onEvent(event);
} else {
/* Screams if a unsupported event gets passed
* Comment this line if you want to ignore
* unsupported events
*/
throw new IllegalArgumentException("Event not supported");
}
}
public static void main(String[] args) {
Foo foo = new Foo();
System.out.println(foo instanceof EventListener); // true
foo.onEvent(new LoginEvent()); // Login
foo.onEvent(new LogoutEvent()); // Logout
}
}
The suppress warnings are there because we are "abusing" type erasure and delegating to two different event listeners based on the event concrete type. I have chosen to do it using a HashMap and the run-time Event class, but there are a lot of other possible implementations. You could use anonymous inner classes like #user949300 suggested, you could include a getEventType discriminator on the Event class to know what do to with each event and so on.
By using this code for all effects you are creating a single EventListener able to handle two kinds of events. The workaround is 100% self-contained (no need to expose the internal EventListeners).
Finally, there is one last issue that may bother you. At compile time Foo type is actually EventListener. Now, API methods out of your control may be expecting parametrized EventListeners:
public void addLoginListener(EventListener<LoginEvent> event) { // ...
// OR
public void addLogoutListener(EventListener<LogoutEvent> event) { // ...
Again, at run-time both of those methods deal with raw EventListeners. So by having Foo implement a raw interface the compiler will be happy to let you get away with just a type safety warning (which you can disregard with #SuppressWarnings("unchecked")):
eventSource.addLoginListener(foo); // works
While all of this may seem daunting, just repeat to yourself "The compiler is trying to trick me (or save me); there is no spoon <T>. Once you scratch your head for a couple of months trying to make legacy code written before Java 1.5 work with modern code full of type parameters, type erasure becomes second nature to you.
You need to use inner or anonymous classes. For instance:
class Foo {
public EventListener<X> asXListener() {
return new EventListener<X>() {
// code here can refer to Foo
};
}
public EventListener<Y> asYListener() {
return new EventListener<Y>() {
// code here can refer to Foo
};
}
}
This is not possible.
But for that you could create two different classes that implement EventListener interface with two different arguments.
public class Login implements EventListener<LoginEvent> {
public void onEvent(LoginEvent event) {
// TODO Auto-generated method stub
}
}
public class Logout implements EventListener<LogoutEvent> {
public void onEvent(LogoutEvent event) {
// TODO Auto-generated method stub
}
}
I was wondering why I can't directly write what's in my battle.resume() method right into my frame.keyPressed() method? If I do so I get a IllegalMonitorStateException, I've found out on the net that this exception is "Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor" so I assumed it has to be directly in the class you want to wait/notify on.
Since I feel like an example is better than 10 lines, so here's a simplified version of what I don't understand, I don't understand why what I linked work and what's between /**/ doesn't, and if it is gonna work all the time.
My Battle class:
public class Battle{
public void run(){
while(true){
System.out.println("START");
synchronized(this){
try{
wait();
}catch(InterruptedException e){}
}
System.out.println("END");
}
}
public void resume(){
synchronized(this){
notify();
}
}
}
My Frame class:
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class Frame extends JFrame implements KeyListener{
private Battle battle;
public Frame(){
this.battle = new Battle();
setTitle("ControlerPanel");
setSize(200, 200);
setResizable(false);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addKeyListener(this);
setVisible(true);
battle.run();
}
public Battle getBattle(){
return battle;
}
#Override
public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_A){
battle.resume();
}
}
#Override
public void keyReleased(KeyEvent e){}
#Override
public void keyTyped(KeyEvent e){}
}
synchronized(battle){
battle.notify();
}
should work. I assume you tried doing synchronized(this) in your keyPressed method, and "this" there referred to the Frame class, not battle. You need to have lock on the object that you are calling notify() on.
I am having two problems when trying to use "updateMessage" in a JavaFX task.
Issue #1
seems to be a known behavior, but I am not yet sure how exactly I can workaround it.This one is not (yet) critical to me.
The problem is that not all the updates I am performing in a background Task are displayed in the UI (at least the UI does not hang/freezes anymore, which was my initial issue).
My Code of the UI part:
TextArea console = new TextArea();
Button button01 = new Button("Start");
button01.setOnAction(new EventHandler() {
#Override
public void handle(Event event) {
if (ms.getState() == State.READY) {
ms.messageProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
console.appendText(newValue+"\n");
}
});
ms.start();
}
}
});
My Service:
public class MyService extends Service<Object> {
#Override
protected Task createTask() {
//here we use "MyTask" first to show problem #1
MyTask ct = new MyTask();
//here we use "MyTask2" first to show problem #2
// MyTask2 ct = new MyTask2();
try {
ct.call();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("MyService end");
return ct;
}
}
My Task (#1)
public class MyTask extends Task<Object> {
#Override
public EventHandler<WorkerStateEvent> call() {
System.out.println("call() is called");
if (Thread.currentThread().getName().equals("JavaFX Application Thread")){//yes, this might not be right, but if I do not do this, my stuff is executed twice because "call()" is called twice, but the textarea area is just updated in the second run (the non javafx application thread).
return null;
} else{
//actually here I want to do some 'heavy' stuff in the background
//and many things of this heavy stuff should be displayed / logged within the UI
//but very likely (hopefully) new messages (updateMessage) will not be send as fast as in the following loop
for (int i=0;i<10000000;i++){
updateMessage("This is update number'"+i+"' from the background thread");
}
Platform.runLater(new Runnable() {
#Override
public void run() {
try{
//here is the chance to get back to the view
}finally{
}
}
});
return null;
}
}
This basically works, but not every single loop is displayed in the UI.
How do I (correctly) make sure every loop is displayed?
Screenshot: Messages are displayed but not for every loop
Issue #2
Currently blocks my attempt to bring my little text-based game into a JavaFX application.
The main problem is that I am able to call "updateMessage" from the Task directly (see above), but not from a another (sub-)class which I would need to bring all message updates from my game (each message describes the progress of the game) to the UI.
The Task I use (Task #2):
public class MyTask2 extends Task<Object> {
#Override
public EventHandler<WorkerStateEvent> call() {
// ...
UITools myTools = new UITools();
myTools.logToUITest("Just one simple message");
// ...
Platform.runLater(new Runnable() {
#Override
public void run() {
try{
//here is the chance to get back to the view
}finally{
}
}
});
return null;
}
and the (sub-)class that I want to use to do the updateMessage (actually in my little game there would be even more classes that are called during the game and almost all of them trigger an update/message).
public class UITools {
public void logToUITest(String message){
updateMessage(message);
//how to allow 'updateMessage' from the Task to be executed from here?
}
This already results in "The method updateMessage(String) is undefined...".
How could I make it possible to call the updateMessage outside of the Task itself?
updateMessage() can only be called from within the call() method of a Task. It's a constraint imposed by the design of the Task class.
The missed message updates are due to the fact that there are too many updates and not all of them are forwarded to the event queue. Try to reduce the number of updates or sleep for a little while to separate them out in time
I create an Observable from a long running operation + callback like this:
public Observable<API> login(){
return Observable.create(new Observable.OnSubscribe<API>() {
#Override
public void call(final Subscriber<? super API> subscriber) {
API.login(new SimpleLoginListener() {
#Override
public void onLoginSuccess(String token) {
subscriber.onNext(API.from(token));
subscriber.onCompleted();
}
#Override
public void onLoginFailed(String reason) {
subscriber.onNext(API.error());
subscriber.onCompleted();
}
});
}
})
}
A successfully logged-in api is the pre-condition for multiple other operations like api.getX(), api.getY() so I thought I could chain these operation with RxJava and flatMap like this (simplified): login().getX() or login().getY().
My biggest problem is now, that I don't have control over when login(callback) is executed. However I want to be able to reuse the login result for all calls.
This means: the wrapped login(callback) call should be executed only once. The result should then be used for all following calls.
It seems the result would be similar to a queue that aggregates subscribers and then shares the result of the first execution.
What is the best way to achieve this? Am I missing a simpler alternative?
I tried code from this question and experiemented with cache(), share(), publish(), refCount() etc. but the wrapped function is called 3x when I do this for all of the mentioned operators:
apiWrapper.getX();
apiWrapper.getX();
apiWrapper.getY();
Is there something like autoConnect(time window) that aggregates multiple successive subscribers?
Applying cache() should make sure login is only called once.
public Observable<API> login() {
return Observable.create(s -> {
API.login(new SimpleLoginListener() {
#Override
public void onLoginSuccess(String token) {
s.setProducer(new SingleProducer<>(s, API.from(token)));
}
#Override
public void onLoginFailed(String reason) {
s.setProducer(new SingleProducer<>(s, API.error()));
}
});
}).cache();
}
If, for some reason you want to "clear" the cache, you can do the following trick:
AtomicReference<Observable<API>> loginCache = new AtomicReference<>(login());
public Observable<API> cachedLogin() {
return Observable.defer(() -> loginCache.get());
}
public void clearLoginCache() {
loginCache.set(login());
}
Ok I think I found one major problem in my approach:
Observable.create() is a factory method so even if every single observable was working as intented, I created many of them. One way to avoid this mistake is to create a single instance:
if(instance==null){ instance = Observable.create(...) }
return instance
I have an activity which loads a data list from the server using loader callbacks. I have to list out the data into a fragment which extends
SherlockListFragment
i tried to commit the fragment using
Fragment newFragment = CategoryFragment.newInstance(mStackLevel,categoryList);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.simple_fragment, newFragment).commit();
in onLoadFinished and it gives an IllegalStateException saying
java.lang.IllegalStateException: Can not perform this action inside of onLoadFinished
I have referred the example in actionbar sherlock, but those examples have loaders within the fragments and not the activity.
Can anybody help me with this o that I can fix it without calling the loader from the fragment!
Atlast, I have found a solution to this problem. Create a handle setting an empty message and call that handler onLoadFinished(). The code is similar to this.
#Override
public void onLoadFinished(Loader<List<Station>> arg0, List<Station> arg1) {
// do other actions
handler.sendEmptyMessage(2);
}
In the handler,
private Handler handler = new Handler() { // handler for commiting fragment after data is loaded
#Override
public void handleMessage(Message msg) {
if(msg.what == 2) {
Log.d(TAG, "onload finished : handler called. setting the fragment.");
// commit the fragment
}
}
};
The number of fragments depend on the requirement.
This method can be mainly used in case of stackFragments, where all fragments have different related functions.
As per the Android docs on the onLoadFinished() method:
Note that normally an application is not allowed to commit fragment transactions while in this call, since it can happen after an activity's state is saved. See FragmentManager.openTransaction() for further discussion on this.
https://developer.android.com/reference/android/app/LoaderManager.LoaderCallbacks.html#onLoadFinished(android.content.Loader, D)
(Note: copy/paste that link into your browser... StackOverflow is not handling it well..)
So you simply should never load a fragment in that state. If you really don't want to put the Loader in the Fragment, then you need to initialize the fragment in your onCreate() method of the Activity, and then when onLoadFinished occurs, simply call a method on your fragment.
Some rough pseudo code follows:
public class DummyFragment {
public void setData(Object someObject) {
//do stuff
}
public class DummyActivity extends LoaderCallbacks<Object> {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Fragment newFragment = DummyFragment.newInstance();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.simple_fragment, newFragment).commit();
getSupportLoaderManager.initLoader(0, null, this)
}
// put your other LoaderCallbacks here... onCreateLoader() and onLoaderReset()
public void onLoadFinished(Loader<Object> loader, Object result) {
Fragment f = getSupportLoaderManager.findFragmentById(R.id.simple_fragment);
f.setData(result);
}
Obviously, you'd want to use the right object.. and the right loader, and probably define a useful setData() method to update your fragment. But hopefully this will point you in the right direction.
As #kwazi answered this is a bad user experience to call FragmentTransition.commit() from onLoadFinished(). I have found a solution for this event by using ProgressDialog.
First created ProgressDialog.setOnDismissListener(new listener) for watching the onLoadFinished().
Further i do progressDialog.show() before getLoaderManager().restartLoader().
And eventually place progressDialog.dismiss() in onLoadFinished().
Such approach allow do not bind main UI thread and Loader's thread.
public class FrPersonsListAnswer extends Fragment
implements
LoaderCallbacks<Cursor>{
private ProgressDialog progressDialog;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_persons_list, container, false);
//prepare progress Dialog
progressDialog = new ProgressDialog(curActivity);
progressDialog.setMessage("Wait...");
progressDialog.setIndeterminate(true);
progressDialog.setOnDismissListener(new OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
//make FragmentTransaction.commit() here;
//but it's recommended to pass control to your Activity
//via an Interface and manage fragments there.
}
});
lv = (ListView) view.findViewById(R.id.lv_out1);
lv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, final View view,
final int position, long id) {
//START PROGRESS DIALOG HERE
progressDialog.show();
Cursor c = (Cursor) parent.getAdapter().getItem(position);
// create Loader
getLoaderManager().restartLoader(1, null, curFragment);
}
});
return view;
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch (loader.getId()) {
case 1:
//dismiss dialog and call progressDialog.onDismiss() listener
progressDialog.dismiss();
break;
default:
break;
}
}