Mvvmcross Bind Click triggered only after focus - android

<CC.CustomEditText
android:id="#+id/receptionIdentityArticle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:theme="#style/ExtendProTheme"
android:maxLength="20"
style="#style/ExtendProTheme.EditText"
android:layout_below="#+id/suppliersSearchInputLabel"
local:MvxBind=" Text ArticleSearchClause, Mode=TwoWay; EnterCommand SearchArticlesCommand; Error Errors['ArticleSearchClause']; Click OnSearchClickCommand" />
So i have a CustomEdit that is different from EditText by overriding two events
this.KeyPress += OnEnterKeyPressed;
this.FocusChange += OnFocusChange;
My problem is that Click command is triggered only second time i click on the EditText. First time it just gets focused then then i click it the second time the Click command triggers. I guess it's how it should work, but i would like to catch the first click it's done on the EditText. An other event maybe it is triggered but I could not find a documentation with all the possible binding on EditText. Any ideas how can i catch the first click on an EditText?

You can use the Touch event instead of Click to get the event to fire on the first click. Unfortunately, the behavior you described is normal to Android (even though confusing) and isn't related to MVVMCross.

As #hankide said, use the Touch event instead. You will need to create a custom binding. I happen to have just dealt with this so here it is:
public class MvxViewTouchBinding
: MvxAndroidTargetBinding
{
private readonly View _view;
private IMvxCommand _command;
public MvxViewTouchBinding(View view) : base(view)
{
_view = view;
_view.Touch += ViewOnTouch;
}
private void ViewOnTouch(object sender, View.TouchEventArgs eventArgs)
{
eventArgs.Handled = false;
if (_command != null)
{
_command.Execute();
}
}
public override void SetValue(object value)
{
_command = (IMvxCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_view.Touch -= ViewOnTouch;
}
base.Dispose(isDisposing);
}
protected override void SetValueImpl(object target, object value)
{
}
public override Type TargetType
{
get { return typeof(IMvxCommand); }
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
and in your Setup.cs put
protected override void FillTargetFactories(MvvmCross.Binding.Bindings.Target.Construction.IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<View>("Touch",
view => new MvxViewTouchBinding(view));
}
Then you can bind to Touch instead of Click.

Related

How to catch Paste event from keyboards Clipboard android?

I want to catch paste event on edittext, with Context menu i can able to catch paste event on edit text like below.
etMobileNumber.customInsertionActionModeCallback = object : ActionMode.Callback {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
if(item?.itemId == android.R.id.paste){
//log.d(“pastevent”)
}
But if text pasted from keyboards clipoard this event is not firing. how to trigger that event ?
I struggled with your issue this afternoon while a customer has filed an issue where the paste keyboard action did not paste the whole ClipData in my EditTexts.
I got to implement a 2FA code input with a unique EditText for each 6 digits to fit the designed UI so I limited each EditText to 1 character.
But the keyboard paste action set only the first character into the currently focused EditText, as I could see it in its TextWatcher.
Going up in the call stack, I saw the action came from the InputConnection.
So what I have done is create my own EditText overriding the InputConnection onCreateInputConnection(EditorInfo) method to return my own InputConnectionWrapper as follow:
public interface OnTFACodePastedListener
{
void onTFACodePasted(#NonNull final String code);
}
public final class TFADigitEditText extends TextInputEditText
implements OnTFACodePastedListener
{
#NonNull
#Override
public InputConnection onCreateInputConnection(#NonNull EditorInfo outAttrs)
{
InputConnection ic = super.onCreateInputConnection(outAttrs);
return new TFAInputConnectionWrapper(this, ic, false);
}
#Override
public void onTFACodePasted(#NonNull String code)
{
// TODO: call void setText(CharSequence).
// In my case, I had to dispatch it to all EditTexts holding the TFA digits
// through their parent custom view.
}
}
public class TFAInputConnectionWrapper extends InputConnectionWrapper
{
private final OnTFACodePastedListener mCodePastedListener;
public TFAInputConnectionWrapper(#NonNull OnTFACodePastedListener pListener,
InputConnection target, boolean mutable)
{
super(target, mutable);
mCodePastedListener = pListener;
}
#Override
public boolean commitText(CharSequence text, int newCursorPosition)
{
String tfaCode = text.toString();
// Just a regex to avoid dispatching incorrect 2FA code for me
if (isValidTFACode(tfaCode))
{
mCodePastedListener.onTFACodePasted(tfaCode);
// Returning false here avoid the wrapped InputConnection
// to dispatch it further has we already handled it.
return false;
}
// On my side, I return false here too to only handle pasting the 2FA code my way
// but it could be useful to keep it in your case
return super.commitText(text, newCursorPosition);
}
}
Finally, keep in mind that we should use the Receive rich content Unified API ending up with the following code:
ViewCompat.setOnReceiveContentListener(myEditText, new String[]{"text/*"}
(view, payload) -> {
ClipData clip = payload.getClip();
if (clip.getItemCount() > 0)
{
String text = clip.getItemAt(0).coerceToText(getContext()).toString();
if (isValidTFACode(text))
{
onTFACodePasted(text);
return null;
}
}
// We should return only unused ClipData.Items, not the whole payload...
return payload;
});
but I don't understand why the keyboard paste action is not handled the same way...
Hope it will be helpful :)

Handle Talkback in a Xamarin app using a virtual DPAD

I have a Xamarin app that was not meant to handle the talkback functionality of android, because for it to work well it had to be build in a specific way.
My app is a little order, and I simply can't make a do-over of the whole thing.
So, what is happening?
My Xamarin app is made with non-native libs, that are not supported by the Talkback, so, when the user turns on the Talkback functionality the app effectively stops receiving the DPAD events since they are handled by the systems Accessibility Service.
That service, gets the events, and tries to handle them within my app, but, since my components are non-native, the system does not recognize them and the DPAD is wasted, hence, the illusion that the DPADs are not working.
So, what do you have to do if you just want to handle the DPADs (and nothing else) yourself with Talkback on?
The answer to this post will contain the code that describes the following behavior:
1. The talkback wont be able to 'talk' about your components
2. The DPAD events will be handled by an Accessibility Delegate
3. A virtual DPAD will handle the navigation
4. The green rectangle used for focus will be disabled, since you wont need it anyway
5. The app will look exactly the same with Talkback on and off
This post was made for educational purposes, since I had a hard time coming up with the solution, and hope the next guy finds it helpfull.
The first step is to create a class that inherits the AccessibilityDelegateCompat in order to create our own Accessibility Service.
class MyAccessibilityHelper : AccessibilityDelegateCompat
{
const string Tag = "MyAccessibilityHelper";
const int ROOT_NODE = -1;
const int INVALID_NODE = -1000;
const string NODE_CLASS_NAME = "My_Node";
public const int NODE_UP = 1;
public const int NODE_LEFT = 2;
public const int NODE_CENTER = 3;
public const int NODE_RIGHT = 4;
public const int NODE_DOWN = 5;
private class MyAccessibilityProvider : AccessibilityNodeProviderCompat
{
private readonly MyAccessibilityHelper mHelper;
public MyAccessibilityProvider(MyAccessibilityHelper helper)
{
mHelper = helper;
}
public override bool PerformAction(int virtualViewId, int action, Bundle arguments)
{
return mHelper.PerformNodeAction(virtualViewId, action, arguments);
}
public override AccessibilityNodeInfoCompat CreateAccessibilityNodeInfo(int virtualViewId)
{
var node = mHelper.CreateNode(virtualViewId);
return AccessibilityNodeInfoCompat.Obtain(node);
}
}
private readonly View mView;
private readonly MyAccessibilityProvider mProvider;
private Dictionary<int, Rect> mRects = new Dictionary<int, Rect>();
private int mAccessibilityFocusIndex = INVALID_NODE;
public MyAccessibilityHelper(View view)
{
mView = view;
mProvider = new MyAccessibilityProvider(this);
}
public override AccessibilityNodeProviderCompat GetAccessibilityNodeProvider(View host)
{
return mProvider;
}
public override void SendAccessibilityEvent(View host, int eventType)
{
Android.Util.Log.Debug(Tag, "SendAccessibilityEvent: host={0} eventType={1}", host, eventType);
base.SendAccessibilityEvent(host, eventType);
}
public void AddRect(int id, Rect rect)
{
mRects.Add(id, rect);
}
public AccessibilityNodeInfoCompat CreateNode(int virtualViewId)
{
var node = AccessibilityNodeInfoCompat.Obtain(mView);
if (virtualViewId == ROOT_NODE)
{
node.ContentDescription = "Root node";
ViewCompat.OnInitializeAccessibilityNodeInfo(mView, node);
foreach (var r in mRects)
{
node.AddChild(mView, r.Key);
}
}
else
{
node.ContentDescription = "";
node.ClassName = NODE_CLASS_NAME;
node.Enabled = true;
node.Focusable = true;
var r = mRects[virtualViewId];
node.SetBoundsInParent(r);
int[] offset = new int[2];
mView.GetLocationOnScreen(offset);
node.SetBoundsInScreen(new Rect(offset[0] + r.Left, offset[1] + r.Top, offset[0] + r.Right, offset[1] + r.Bottom));
node.PackageName = mView.Context.PackageName;
node.SetSource(mView, virtualViewId);
node.SetParent(mView);
node.VisibleToUser = true;
if (virtualViewId == mAccessibilityFocusIndex)
{
node.AccessibilityFocused = true;
node.AddAction(AccessibilityNodeInfoCompat.ActionClearAccessibilityFocus);
}
else
{
node.AccessibilityFocused = false;
node.AddAction(AccessibilityNodeInfoCompat.FocusAccessibility);
}
}
return node;
}
private AccessibilityEvent CreateEvent(int virtualViewId, EventTypes eventType)
{
var e = AccessibilityEvent.Obtain(eventType);
if (virtualViewId == ROOT_NODE)
{
ViewCompat.OnInitializeAccessibilityEvent(mView, e);
}
else
{
var record = AccessibilityEventCompat.AsRecord(e);
record.Enabled = true;
record.SetSource(mView, virtualViewId);
record.ClassName = NODE_CLASS_NAME;
e.PackageName = mView.Context.PackageName;
}
return e;
}
public bool SendEventForVirtualView(int virtualViewId, EventTypes eventType)
{
if (mView.Parent == null)
return false;
var e = CreateEvent(virtualViewId, eventType);
return ViewParentCompat.RequestSendAccessibilityEvent(mView.Parent, mView, e);
}
public bool PerformNodeAction(int virtualViewId, int action, Bundle arguments)
{
if (virtualViewId == ROOT_NODE)
{
return ViewCompat.PerformAccessibilityAction(mView, action, arguments);
}
else
{
switch (action)
{
case AccessibilityNodeInfoCompat.ActionAccessibilityFocus:
if (virtualViewId != mAccessibilityFocusIndex)
{
if (mAccessibilityFocusIndex != INVALID_NODE)
{
SendEventForVirtualView(mAccessibilityFocusIndex, EventTypes.ViewAccessibilityFocusCleared);
}
mAccessibilityFocusIndex = virtualViewId;
mView.Invalidate();
SendEventForVirtualView(virtualViewId, EventTypes.ViewAccessibilityFocused);
// virtual key event
switch (virtualViewId)
{
case NODE_UP:
HandleDpadEvent(Keycode.DpadUp);
break;
case NODE_LEFT:
HandleDpadEvent(Keycode.DpadLeft);
break;
case NODE_RIGHT:
HandleDpadEvent(Keycode.DpadRight);
break;
case NODE_DOWN:
HandleDpadEvent(Keycode.DpadDown);
break;
}
// refocus center
SendEventForVirtualView(NODE_CENTER, EventTypes.ViewAccessibilityFocused);
return true;
}
break;
case AccessibilityNodeInfoCompat.ActionClearAccessibilityFocus:
mView.RequestFocus();
if (virtualViewId == mAccessibilityFocusIndex)
{
mAccessibilityFocusIndex = INVALID_NODE;
mView.Invalidate();
SendEventForVirtualView(virtualViewId, EventTypes.ViewAccessibilityFocusCleared);
return true;
}
break;
}
}
return false;
}
private void HandleDpadEvent(Keycode keycode)
{
//Here you know what DPAD was pressed
//You can create your own key event and send it to your app
//This code depends on your own application, and I wont be providing the code
//Note, it is important to handle both, the KeyDOWN and the KeyUP event for it to work
}
}
Since the code is a bit large, I'll just explain the crutal parts.
Once the talkback is active, the dictionary (from our view bellow) will be used to create a virtual tree node of our virtual DPAD. With that in mind, the function PerformNodeAction will be the most important one.
It handles the actions once a virtual node was focused by the Accessibility system, based on the provided id of the virtual element, there are two parts, the first one is the ROOT_NODE, which is the view iteslf that contains our virtual dpad, which for the most part can be ignored, but the seond part is where the handling is done.
The second part is where the actions ActionAccessibilityFocus and ActionClearAccessibilityFocus are handled. The two of witch are both important, but the first one is where we can finally handle our virtual dpad.
What is done here is that with the provided virtual ID from the dictionary, we know which DPAD was selected (virtualViewId). Based on the selected DPAD, we can perform the action we want in the HandleDpadEvent function. What is important to notice, is that after we handle the selecteds DPAD event, we will refocus our CENTER node, in order to be ready to handle the next button press. This is very important, since, you dont want to find yourself in a situation where you go DOWN, and then UP, just for the virtual dpad to focus the CENTER pad.
So, I'll epeat myself, the refocusing of the CENTER pad after the previous' DPAD event was handled needs to be done in order for us to know EXACTLY where we will be after the next DPAD button was pressed!
There is one function that I wont post here, since the code for it is very specific for my app, the function is HandleDpadEvent, there you must create a keydown and a keyup event and send it to your main activity where the function onKeyDown/Up will be triggered. Once you do that, the delegate is done.
And once the Delegate is done, we have to make our view like this:
/**
* SimplestCustomView
*/
public class AccessibilityHelperView : View
{
private MyAccessibilityHelper mHelper;
Dictionary<int, Rect> virtualIdRectMap = new Dictionary<int, Rect>();
public AccessibilityHelperView(Context context) :
base(context)
{
Init();
}
public AccessibilityHelperView(Context context, IAttributeSet attrs) :
base(context, attrs)
{
Init();
}
public AccessibilityHelperView(Context context, IAttributeSet attrs, int defStyle) :
base(context, attrs, defStyle)
{
Init();
}
public void Init()
{
this.SetFocusable(ViewFocusability.Focusable);
this.Focusable = true;
this.FocusedByDefault = true;
setRectangle();
mHelper = new MyAccessibilityHelper(this);
ViewCompat.SetAccessibilityDelegate(this, mHelper);
foreach (var r in virtualIdRectMap)
{
mHelper.AddRect(r.Key, r.Value);
}
}
private void setRectangle()
{
virtualIdRectMap.Add(MRAccessibilityHelper.NODE_CENTER, new Rect(1, 1, 2, 2));
virtualIdRectMap.Add(MRAccessibilityHelper.NODE_LEFT, new Rect(0, 1, 1, 2));
virtualIdRectMap.Add(MRAccessibilityHelper.NODE_UP, new Rect(1, 0, 2, 1));
virtualIdRectMap.Add(MRAccessibilityHelper.NODE_RIGHT, new Rect(2, 1, 3, 2));
virtualIdRectMap.Add(MRAccessibilityHelper.NODE_DOWN, new Rect(1, 2, 2, 3));
}
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
}
}
That view looks like this:
What is to notice?
The size of the node pads is in pixels, and they will be found on the top left corner of your app.
They are set to that single pixel size, because the Talkback functionality would otherwise select the first node pad that was added to the dictionary with a green rectangle (thats standard behavior for talkback)
All the rectangles in the view are added to a dictionary that will be used in our own Accessibility Delegate, to mention here is that the CENTER pad was added first, and therefor will be in focus once the talkback is activated by default
The Init function
The Init function is crutial for this, there we will create our view, and set some talkback parameters nessessary for our virtual dpad to be recognized by the systems own Accessibility Service.
Also, there will our Accessibility Delegate be initialized and our dictionary with all the created DPADs.
Ok, so far, we made a Delegate and a View, I placed them both in the same file, so they can see each other. But it is not a must.
So what now? We must add the AccessibilityHelperView to our app, in the MainActivity.cs file
AccessibilityHelperView mAccessibilityHelperView;
In the OnCreate function, you can add the following code to initiate the view:
mAccessibilityHelperView = new AccessibilityHelperView(this);
In the OnResume function, you can check if the talkback is on or off, based on the result, you can add or remove the mAccessibilityHelperView from your mBackgroundLayout(AddView, and RemoveView).
The OnResume function should look like this:
if (TalkbackEnabled && !_isVirtualDPadShown)
{
mBackgroundLayout.AddView(mAccessibilityHelperView);
_isVirtualDPadShown = true;
}
else if (!TalkbackEnabled && _isVirtualDPadShown)
{
mBackgroundLayout.RemoveView(mAccessibilityHelperView);
_isVirtualDPadShown = false;
}
The TalkbackEnabled variable is a local one that checks if the Talkback service is on or off, like this:
public bool TalkbackEnabled
{
get
{
AccessibilityManager am = MyApp.Instance.GetSystemService(Context.AccessibilityService) as AccessibilityManager;
if (am == null) return false;
String TALKBACK_SETTING_ACTIVITY_NAME = "com.android.talkback.TalkBackPreferencesActivity";
var serviceList = am.GetEnabledAccessibilityServiceList(FeedbackFlags.AllMask);
foreach (AccessibilityServiceInfo serviceInfo in serviceList)
{
String name = serviceInfo.SettingsActivityName;
if (name.Equals(TALKBACK_SETTING_ACTIVITY_NAME))
{
Log.Debug(LogArea, "Talkback is active");
return true;
}
}
Log.Debug(LogArea, "Talkback is inactive");
return false;
}
}
That should be all you need to make it work.
Hope I could help you out.

ScrollView Custom renderer no longer working on Xamarin Forms 2.x

Want to add simple paging to ScrollView.
On iOS it was easy adding a customer renderer like this:
[assembly:ExportRenderer (typeof(ScrollView), typeof(JustEat.Ordertracker.Touch.Renderers.ExtendedScrollViewRenderer))]
namespace JustEat.Ordertracker.Touch.Renderers
{
public class ExtendedScrollViewRenderer : ScrollViewRenderer
{
protected override void OnElementChanged (VisualElementChangedEventArgs e)
{
base.OnElementChanged (e);
UIScrollView iosScrollView = (UIScrollView)NativeView;
iosScrollView.PagingEnabled = true;
iosScrollView.ShowsHorizontalScrollIndicator = false;
}
}
}
So doesn't look like android has paging flag so was going to implement with touch events or similar, but can't get any events to trigger on Android side. So have tried hooking up events and overriding:
[assembly:ExportRenderer (typeof(ScrollView), typeof(JustEat.Ordertracker.Touch.Renderers.ExtendedScrollViewRenderer))]
namespace JustEat.Ordertracker.Touch.Renderers
{
public class ExtendedScrollViewRenderer : ScrollViewRenderer
{
protected override void OnElementChanged (VisualElementChangedEventArgs e)
{
base.OnElementChanged (e);
global::Android.Widget.ScrollView droidScrollView = (global::Android.Widget.ScrollView)this;
droidScrollView.HorizontalScrollBarEnabled = false;
droidScrollView.Drag += delegate
{
Console.WriteLine("Drag");
};
}
public override bool OnTouchEvent(global::Android.Views.MotionEvent ev)
{
Console.WriteLine("OnTouchEvent");
return base.OnTouchEvent(ev);
}
}
}
The OnElementChanged definitely gets called as I hit a breakpoint, but the events do nothing, neither does setting HorizontalScrollBarEnabled to false, or even setting .Enabled to false. It's like I have access to a different object?
Found an excellent git repo using same technique to do same thing
https://github.com/chrisriesgo/xamarin-forms-carouselview/issues/24
which works great with 1.x version of forms it uses, but upgrade it to 2.x and it no longer works for above reason.

Detect when burn-in protection has activated?

I can check to see if the burn-in protection property is enabled, but is there a way to tell when burn-in mode is currently active? Like specifically when the screen shifts.
Basically something like "onAmbientModeChanged" for burn in.
Thanks!
In an activity, extend WearableActivity and override onEnterAmbientMode, you have in parameter a Bundle where you can retrieve the property wanted.
(check this WearableActivity)
#Override
public void onEnterAmbient(Bundle ambientDetails) {
super.onEnterAmbient(ambientDetails);
boolean burnIn = ambientDetails.getBoolean(EXTRA_BURN_IN_PROTECTION);
boolean lowBit = ambientDetails.getBoolean(EXTRA_LOWBIT_AMBIENT);
}
In a CanvasWatchFaceService.Engine, override onPropertiesChanged :
#Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
boolean lowBit = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
boolean burnIn = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
}
Override onAmbientModeChanged(boolean inAMbientMode), it is called whenever the watchface switches from interactive to ambient mode and vice versa :
#Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (mState.isAmbient() != inAmbientMode) {
mState.setAmbient(inAmbientMode);
//make your updates on your drawing parameters if needed
invalidate();
}
}

how to correctly extends the class from the library Otto?

I am use Otto library in my project. And me need any functionality from this library wherein not.I want to do so:
Bus.post(MessageType.USER_SIGN_UP_SUCCESS, user);
and in my method realise do so:
#Subscribe({MessageType.USER_LOGGED_IN_SUCCESS, MessageType.USER_SIGN_UP_SUCCESS})
public void getUserFromServer(User user) {
,,,,,,,,,,,,,,
}
for this I had to copy all Otto classes from githab, and change them. I could not implement from Otto because some variables private.
and changed the access modifiers in Bus class and extends from it.
public class SkipBus extends Bus {
public void post(MessageType messageType, Object event) {
if (event == null) {
throw new NullPointerException("Event to post must not be null.");
}
enforcer.enforce(this);
Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
boolean dispatched = false;
for (Class<?> eventType : dispatchTypes) {
Set<EventHandler> wrappers = getHandlersForEventType(eventType);
if (null == wrappers || wrappers.isEmpty()) {
continue;
}
dispatched = true;
for (EventHandler wrapper : wrappers) {
Subscribe annotation = wrapper.method.getAnnotation(Subscribe.class);
boolean isFounded = false;
MessageType messageTypes[] = annotation.value();
for (MessageType type : messageTypes) {
if (type == messageType) {
isFounded = true;
break;
}
}
if (isFounded) {
enqueueEvent(event, wrapper);
}
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}
}
but for this I had to copy all the classes in my project.
tell me how can I make it easier? or tell me another library that can do what I want
Actually Otto was designed to separate the message type using Object's type.
#Subscribe
public void eventReceived(UserSignUpEvent user) {
}
#Subscribe
public void eventReceived(UserLoginEvent user) {
}
or
MainActivity.java
#Subscribe
public void eventReceived(User user) {
if (user.getMessageType() == MessageType.USER_SIGN_UP_SUCCESS) {
}
}
SecondActivity.java
#Subscribe
public void eventReceived(User user) {
if (user.getMessageType() == MessageType.USER_LOGIN_SUCCESS) {
}
}
You have no need to separate the MessageType like this (and you should not). I suggest you to change the code design pattern to what that Otto is designed for. Otherwise, you have to copy the whole Otto source code and edit like you are currently doing.
Otto itself is pretty simple so there's no need to extend it. You just create event and event handler for it. Event can be any object thus you can do something like:
public enum LoginType {
SUCCESS,
FAILED
}
In your login processor:
private void login(...) {
// process login
// if everything is ok
if(...) {
bus.post(LoginType.SUCCESS);
} else {
bus.post(LoginType.FAILED);
}
}
And handle this event wherever you need:
#Subscribe
public void onLogin(LoginType loginType) {
switch(loginType) {
case SUCCESS:
// do what you need
break;
case FAILED:
// show an error or something
break;
}
}
So no need to extend library, you just have to design your events in a proper way.

Categories

Resources