disable to android system back button using backButtonDispatcher (in flutter) - java

we all know about WillPopScope...
example:
return WillPopScope(
onWillPop: () async => return false,
child: Scaffold(...));
That works fine in most cases but not in mine. I'm using Beamer in a complicated way and MaterialApp.router so even if I use WillPopScope the back button still has an effect (exiting the app no matter where I'm at).
So I'd like to disable to back button at a deeper level. And the next level down looks like using the backButtonDispatcher in the MaterialApp.router like so:
class DoNothingBackButtonDispatcher extends BackButtonDispatcher
with WidgetsBindingObserver {
DoNothingBackButtonDispatcher();
#override
void addCallback(ValueGetter<Future<bool>> callback) {}
#override
void removeCallback(ValueGetter<Future<bool>> callback) {}
#override
Future<bool> didPopRoute() => invokeCallback(Future<bool>.value(false));
#override
ChildBackButtonDispatcher createChildBackButtonDispatcher() => ChildBackButtonDispatcher(this);
#override
void deferTo(ChildBackButtonDispatcher child) {}
#override
void forget(ChildBackButtonDispatcher child) {}
#override
Future<bool> invokeCallback(Future<bool> defaultValue) =>
SynchronousFuture<bool>(false);
bool onPop(BuildContext context, Route<dynamic> route) => false;
#override
void takePriority() {}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final rootBeamerDelegate = BeamerDelegate(
initialPath: Sailor.initialPath,
locationBuilder: RoutesLocationBuilder(
routes: {
'*': (context, state, data) => const HomePage(),
},
),
);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerDelegate: rootBeamerDelegate,
routeInformationParser: BeamerParser(),
backButtonDispatcher: DoNothingBackButtonDispatcher(), // <----- HERE
);
}
}
Unfortunately, even this closes the app when the user hits the system back button.
So, the question is:
How do I implement the abstract BackButtonDispatcher class in order to essentially ignore the button press?
Thanks so much for your help!

Related

Good way to prevent duplicated event handle

I have a common problem since the first day I start coding: avoid handling an event (button click,..) multiple times. Most of the time, I come up with a simple solution (which add a boolean flag to check) like this:
private boolean isProcessingClick = false;
#Override
public void onClick(View v) {
onLoginButtonClick();
}
private void onLoginButtonClick() {
if (isProcessingClick)
return;
isProcessingClick = true;
// Do something..
// Update some UIs..
isProcessingClick = false;
}
This worked fine. But as the class go bigger with more features & events everything started going wrong. We need to create too many boolean flags which doesn't mean anything about the business and should not be a field of this class.
Does anyone has better solution for this?
The restriction can be supported with a wrapper (if you need to protect click of each button independently).
class MyOnClickListener implements View.OnClickListener {
private final View.OnClickListener internal;
private final AtomicBoolean isProcessingClick = new AtomicBoolean();
public MyOnClickListener(View.OnClickListener internal) {
this.internal = internal;
}
#Override
public void onClick(View v) {
try {
boolean noProcessing = this.isProcessingClick.compareAndSet(false, true);
if(noProcessing) {
internal.onClick(v);
} else {
// it's good to show some alert for the user here
}
} finally {
isProcessingClick.set(false);
}
}
}
I'm not android developer so AtomicBoolean was used in case that 'onClick' could be invoked by different threads.

How I would use SwitchMap (RXJAVA) in my code?

I'm new to Android development and am currently trying to make a simple MVC app that works with Rest API.
API calls are made without using Retrofit, although this is not so important. The main catch is that using Observable with debounce and SwitchMap I still get too many API calls (and the extra ones should be discarded). The function is called when text is entered (EditText Listener with TextWatcher). And when administered continuously without delay word, every symbol processed by the server and should only be administered when not within 600 milliseconds. Please help me.
public Observable<String> getObservable(final String s){
return Observable
.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext(model.translateText(s));
}
});
}
public Observer<String> observer = new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
mainView.hideProgress();
mainView.showResult(s);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
public void onEditTextChange(String textForTranslate){
mainView.showProgress();
getObservable(textForTranslate)
.debounce(600,TimeUnit.MILLISECONDS)
.switchMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(String s) throws Exception {
return Observable.just(s);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
You are creating a new observable every time a character is typed. So multiple observers are created with each having separate debounce (time throttle) and switch but they are not reused. Instead you create a new observable whenever text changes and start rx chain on it.
You need to create a single PublishSubject
private final PublishSubject<String> querySubject = PublishSubject.create();
that emits entered text/query whenever text is changed. Use it in your callback:
public void onEditTextChange(String textForTranslate) {
querySubject.onNext(textForTranslate);
}
And in your main function, subscribe to observable:
querySubject
.debounce(600, TimeUnit.MILLISECONDS)
.distinctUntilChanged()
.switchMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(String s) throws Exception {
// perform api call or any other operation here
return Observable.just(s);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
Debounce operator emits single item only after given time (600 ms) has passed. It ignores items if current item is being processed and given time has not passed.
distinctUntilChanged helps in reducing processing of same query.

RxJava2 flatMap create duplicate events

I'm relatively new on RxJava2 and I'm getting some weird behaviors, so it's likely that I'm using the tool on the wrong way.
It's a fairly big project, but I've separated the snippet below as a minimum reproducible code:
Observable
.interval(333, TimeUnit.MILLISECONDS)
.flatMap(new Function<Long, ObservableSource<Integer>>() {
private Subject<Integer> s = PublishSubject.create();
private int val = 0;
#Override public ObservableSource<Integer> apply(Long aLong) throws Exception {
val++;
s.onNext(val);
return s;
}
})
.subscribe(new Consumer<Integer>() {
#Override public void accept(Integer integer) throws Exception {
Log.w("value: %s", integer);
}
});
This code simulates events from my rx-stream using an .interval and a flatMap receive those events "do some processing" and uses a Subject to push results down the stream.
The stream is an ongoing process which will have several several events.
This minimum code is silly because I'm pushing only on the apply callback, but in the real case there're several possible moments that a push can happen and the number of events being received during apply is not the same amount that will be sent via the Subject.
What I expected to see with this code is:
value: 2 // 1 got skipped because onNext is called before there's a subscriber.
value: 3
value: 4
value: 5
value: 6 ... etc
what I actually got is:
value: 2
value: 3
value: 3 // 3 twice
value: 4
value: 4
value: 4 // 4 repeated 3 times
value: 5
value: 5
value: 5
value: 5 // 5 repeated 4 times
value: 6
value: 6
value: 6
value: 6
value: 6 // 6 repeated 5 times
... etc
I've also tried to have an Observable<Integer> o = s.share(); and returning it, or return directly s.share(); with the same results.
I kind of understand why this is happening. The ObservableSource gets subscribed again n again n again so there're more events on every loop.
The question:
How can I achieve my expected behavior?
(in case my expected behavior was not clear, please ask more on the comments)
Your PublishSubject is subscribed to multiple times, once per item from interval().
Edit: You will need to pass in a new PublishSubject each time (switch to BehaviorSubject if you'd like to retain the first/last emission); pass that to the long-running process, and ensure that its onComplete is called properly when the long-running process finishes.
Edit
After recent comments I could come up with this kind of a solution:
class MyBluetoothClient {
private PublishSubject<BTLEEvent> statusPublishSubject = PublishSubject.create()
public Observable<BTLEEvent> getEventObservable() {
return statusPublishSubject
}
private void publishEvent(BTLEEvent event) {
statusPublishSubject.onNext(event)
}
public void doStuff1() {
// do something that returns:
publishEvent(BTLEEvent.someEvent1)
}
public void doStuff2() {
// do something else that eventually yields
publishEvent(BTLEEvent.someEvent2)
}
}
And you use it in this way:
MyBluetoothClient client = MyBluetoothClient()
client
.getEventObservable()
.subscribe( /* */ )
///
client.doStuff1()
///
client.doStuff2
Original answer
Will this do?
Observable
.interval(333, TimeUnit.MILLISECONDS)
.flatMap(new Function<Long, ObservableSource<Integer>>() {
private int val = 0;
#Override public ObservableSource<Integer> apply(Long aLong) throws Exception {
val++;
return Observable.just(val);
}
})
.subscribe(new Consumer<Integer>() {
#Override public void accept(Integer integer) throws Exception {
Log.w("value: %s", integer);
}
});
So here is the answer I came up with. I'll mark #Tassos answer as correct as he pointed me out on the right path.
First I need a CachedSubject (a subject that caches items while there's no observers and dispatches them as soon as an observer connects), this is necessary to make sure emissions from inside the apply really gets through. The class mostly wraps a PublishSubject.
class CachedSubject<T> extends Subject<T> {
private PublishSubject<T> publishSubject = PublishSubject.create();
private Queue<T> cache = new ConcurrentLinkedQueue<>();
#Override public boolean hasObservers() {
return publishSubject.hasObservers();
}
#Override public boolean hasThrowable() {
return publishSubject.hasThrowable();
}
#Override public boolean hasComplete() {
return publishSubject.hasComplete();
}
#Override public Throwable getThrowable() {
return publishSubject.getThrowable();
}
#Override protected void subscribeActual(Observer<? super T> observer) {
while (cache.size() > 0) {
observer.onNext(cache.remove());
}
publishSubject.subscribeActual(observer);
}
#Override public void onSubscribe(Disposable d) {
publishSubject.onSubscribe(d);
}
#Override public void onNext(T t) {
if (hasObservers()) {
publishSubject.onNext(t);
} else {
cache.add(t);
}
}
#Override public void onError(Throwable e) {
publishSubject.onError(e);
}
#Override public void onComplete() {
publishSubject.onComplete();
}
}
then I use this class with a switchMap:
Observable
.interval(1000, TimeUnit.MILLISECONDS)
.switchMap(new Function<Long, ObservableSource<Integer>>() {
private Subject<Integer> s = new CachedSubject<>();
private int val = 0;
#Override public ObservableSource<Integer> apply(Long aLong) throws Exception {
val++;
s.onNext(val);
return s;
}
})
.subscribe(new Consumer<Integer>() {
#Override public void accept(Integer integer) throws Exception {
Log.w("value: %s", integer);
}
});
This effectively allows me to receive any number of events on the apply<T t> method and have only 1 Consumer subscribed to it, receiving all the events from it.

Retrofit + rxJava: how to implement iterable N requests?

I have a problem to implement following problem: I'm making a request that fetches all active leagues. Then, for each of them I need to make another request to grab the matches. I think it's possible to implement the solution using flatMapIterable, but don't know how.
For now I have following retrofit interfaces:
public interface LeaguesApi
{
#GET("/api/get-leagues")
Observable<ArrayList<League>> getLeagues(#Query("active_only") boolean activeOnly);
}
public interface LeagueApi
{
#GET("/api/get-league-fixtures/{leagueId}")
Observable<ArrayList<Match>> getMatchesPlayed(#Path("leagueId") int leagueId, #Query("played") boolean played);
}
Please advise how to iterate through all leagues in order to perform getMatchesPlayed for each of them. Best would be without lambda expressions, since I'm not using them in my project.
Try this code:
leaguesApi // your REST adapter from retrofit
.getLeagues(true) // fetch leagues
.flatMapIterable(leagues -> leagues) //Transform from ArrayList<Liague> to Observable<Liague>
.flatMap(l -> leagueApi.getMatchesPlayed(l.getId(), true))
.subscribe(
(match) -> {/*This is onNext*/},
t -> t.printStackTrace(), //onError
() -> {/*onComplete*/}
);
UPDATE:
Without lambdas:
leaguesApi // your REST adapter from retrofit
.getLeagues(true) // fetch leagues
.flatMapIterable(new Func1<ArrayList<League>, Iterable<?>>() {
#Override
public Iterable<?> call(ArrayList<League> leagues) {
return leagues;
}
}) //Transform from ArrayList<Liague> to Observable<Liague>
.flatMap(new Func1<League, Observable<Match>>() {
#Override
public Observable<Match> call(League l) {
return leagueApi.getMatchesPlayed(l.getId(), true);
}
})
.subscribe(
new Action1<Match>() {
#Override
public void call(Match match) {
//onNext
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
//onError
}
},
new Action0() {
#Override
public void call() {
//onComplete
}
}
);
I'd change that API so it reads like this otherwise you lose a lot of the flexibility of streams:
public interface LeaguesApi
{
#GET("/api/get-leagues")
Observable<League> getLeagues(#Query("active_only") boolean activeOnly);
}
public interface LeagueApi
{
#GET("/api/get-league-fixtures/{leagueId}")
Observable<Match> getMatchesPlayed(#Path("leagueId") int leagueId, #Query("played") boolean played);
}
Can you do that?
If not then to get Observable<T> from Observable<ArrayList<T>> you do:
observable.flatMapIterable(
new Func1<ArrayList<T>, ArrayList<T>>() {
#Override
public ArrayList<T> call(ArrayList<T> list) {
return list;
}
});
much nicer to just say observable.flatMapIterable(x -> x) of course.
To get all played matches for all active leagues just do this:
Observable<League> leagues= getLeagues(true);
Observable<Match> matches =
leagues.flatMap( league -> getMatchesPlayed(league.leagueId, true));
or without lambdas (I wish you hadn't asked for that)
Observable<Match> matches = leagues.flatMap(
new Func1<League, Observable<Match>> () {
#Override
public Observable<Match> call(League league) {
return getMatchesPlayed(league.leagueId, true);
}
});

Handle DIV add child node event with GWT

Is there such a thing in GWT to handle event when elements are added to a specific DIV?
From this answer there is jQuery solution:
Fire jQuery event on div change
$('body').on('DOMNodeInserted', '#common-parent', function(e) {
if ($(e.target).attr('class') === 'myClass') {
console.log('hit');
}
});
You can integrate jquery method by using jsni.
At first you need to create corresponding gwt like event:
package com.test.gwt;
import com.google.gwt.event.shared.GwtEvent;
public class DOMNodeInsertedEvent extends GwtEvent<DOMNodeInsertedEventHandler> {
public static Type<DOMNodeInsertedEventHandler> TYPE = new Type<DOMNodeInsertedEventHandler>();
public Type<DOMNodeInsertedEventHandler> getAssociatedType() {
return TYPE;
}
protected void dispatch(DOMNodeInsertedEventHandler handler) {
handler.onDOMNodeInserted(this);
}
}
and gwt like handler:
package com.test.gwt;
import com.google.gwt.event.shared.EventHandler;
public interface DOMNodeInsertedEventHandler extends EventHandler {
void onDOMNodeInserted(DOMNodeInsertedEvent event);
}
then implement jsni wrapping function
public native void addDOMNodeInsertedEventHandler(Widget widget, Element element)/*-{
$wnd.$(element).bind('DOMNodeInserted', function (event) {
var gwtEvent = #com.test.gwt.DOMNodeInsertedEvent::new()();
widget.#com.google.gwt.user.client.ui.Widget::fireEvent(Lcom/google/gwt/event/shared/GwtEvent;)(gwtEvent);
});
}-*/;
and at last add your handler to corresponding panel
FlowPanel flowPanel = new FlowPanel();
flowPanel.addHandler(new DOMNodeInsertedEventHandler() {
#Override
public void onDOMNodeInserted(DOMNodeInsertedEvent event) {
Window.alert("Inserted");
}
}, DOMNodeInsertedEvent.TYPE);
addDOMNodeInsertedEventHandler(flowPanel, flowPanel.getElement());

Categories

Resources