6

How to prevent Android Snackbar from dismissing on setAction onclick, Thanks

Snackbar.make(rootlayout, "Hello SnackBar!", Snackbar.LENGTH_INDEFINITE)
   .setAction("Undo", new View.OnClickListener() {
       @Override
       public void onClick(View v) {
           // Snackbar should not dismiss
       }
   })
   .show();
azizbekian
  • 60,783
  • 13
  • 169
  • 249
Eleazer Toluan
  • 233
  • 4
  • 14

3 Answers3

9

Here is a somewhat cleaner solution for achieving this, which doesn't require reflection. It's based on knowning the view ID of the button within the Snackbar. This is working with version 27.1.1 of the support library, but may no longer work in a future version if the view ID will be changed!

First, set your snackbar action using an empty OnClickListener:

snackbar.setAction("Save", new View.OnClickListener() {
    @Override
    public void onClick(View v) {}
});

Afterwards, add a callback to the snackbar (before showing it). Override the onShown function, find the button using R.id.snackbar_action and add your own OnClickListener to it. The snackbar will only be dismissed when manually calling snackbar.dismiss(), or by swiping if the snackbar is attached to a CoordinatorLayout (how to disable the swipe is a different SO question).

snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
    @Override
    public void onShown(Snackbar transientBottomBar) {
        super.onShown(transientBottomBar);

        transientBottomBar.getView().findViewById(R.id.snackbar_action).setOnClickListener(new View.OnClickListener() {
            // your code here
        }
Ovidiu
  • 8,204
  • 34
  • 45
1

First, by design Snackbar shouldn't stay there after the action click, that's why it is non-configurable parameter.

Diving into code I could find enough seams in order to do that by reflection.

public static void doNotHideSnackbar(Snackbar snackbar) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException {
    final Field sHandler = BaseTransientBottomBar.class.getDeclaredField("sHandler");
    sHandler.setAccessible(true);
    final Method handleMessage = Handler.class.getMethod("handleMessage", Message.class);
    final Handler originalHandler = (Handler) sHandler.get(snackbar);
    Handler decoratedHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {
            switch (message.what) {
                case 0:
                    try {
                        handleMessage.invoke(originalHandler, Message.obtain(originalHandler, 0));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                    return true;
            }
            return false;
        }
    });
    sHandler.set(snackbar, decoratedHandler);
}

This is tested and works with support library version 25.3.1.

Usage

final Snackbar snackbar = Snackbar.make(root, "Hello SnackBar!", Snackbar.LENGTH_INDEFINITE).setAction("Undo", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(v.getContext(), "clicked", Toast.LENGTH_SHORT).show();
    }
});

snackbar.show();

try {
    doNotHideSnackbar(snackbar);
} catch (NoSuchFieldException e) {
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

Result

enter image description here

BEWARE, this is not the solution you should prefer to stick with, as long as API may change from version to version. You'd better consider implementing your custom Snackbaralike view. But as a fast workaround you can consider using this reflectioned version.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • I've played a bit with this, and I found something strange... dismissing the `Snackbar` is _partially_ prevented: neither calling `dismiss()`, nor setting the duration (not indefinite) works, only swiping on screen (but callbacks are not called in this case either)! I also tried to implement handling and forwarding the other case to the original handler in the switch statement (0: show, 1: dismiss), with no result close to the original one. – nvi9 May 30 '17 at 22:27
  • thanks a lot but how to hide it next when required ? – Hoby May 24 '18 at 13:25
-1

Better late than never - here's how i did it.

private fun showSnackbar() {
        if(snackbar == null) {
            //init snackbar
            snackbar = Snackbar.make(mainCoordinator, R.string.snackbar_no_network, Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.snackbar_no_network_action) {
                        checkConnection()
                    } // action text on the right side
                    .setActionTextColor(ContextCompat.getColor(context, R.color.snack_green))
            //set background color
            snackbar!!.view.setBackgroundColor(ContextCompat.getColor(context, R.color.main_dark_gray))
        }
        //show
        snackbar!!.show()
    }

private val handler = Handler()
private fun checkConnection() {
    handler.postDelayed(checkConnectionRunnable, 500)
}

private val checkConnectionRunnable = Runnable {
    if (!NetworkUtil.isOnline(context)){
        showSnackbar()
    }
}
user3783123
  • 544
  • 3
  • 15