Making a Snackbar Without a View?

JavaAndroidXmlCrashAndroid Snackbar

Java Problem Overview


I want to show a snackbar as soon as the user opens the Google Maps activity, but the thing is that there's no views in the activity to use as the first parameter of the activity (in the findViewById() of Snackbar.make()). What do I put there? Here's the java class code:

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

private GoogleMap mMap;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_maps);

    // Obtain the SupportMapFragment and get notified when the map is ready to be used.
    SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);
    mapFragment.getMapAsync(this);
}

@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    mMap.setBuildingsEnabled(true);
    mMap.getUiSettings().setZoomControlsEnabled(true);
    float cameraZoom = 17;
    LatLng location = new LatLng(43.404032, -80.478184);
    mMap.addMarker(new MarkerOptions().position(location).title("49 McIntyre Place #18, Kitchener, ON N2R 1G3"));
    CameraUpdateFactory.newLatLngZoom(location, cameraZoom);
    Snackbar.make(findViewById(/*WHAT DO I PUT HERE?*/), "Click the pin for more options", Snackbar.LENGTH_LONG).show();
}
}

Also, here is the activity xml code:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="ca.davesautoservice.davesautoservice.MapsActivity" />

And lastly, here's the stacktrace error:

08-03 11:42:21.333 3901-3901/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: ca.davesautoservice.davesautoservice, PID: 3901
    java.lang.NullPointerException
        at android.support.design.widget.Snackbar.<init>(Snackbar.java:183)
        at android.support.design.widget.Snackbar.make(Snackbar.java:215)
        at ca.davesautoservice.davesautoservice.MapsActivity.onMapReady(MapsActivity.java:48)
        at com.google.android.gms.maps.SupportMapFragment$zza$1.zza(Unknown Source)
        at com.google.android.gms.maps.internal.zzo$zza.onTransact(Unknown Source)
        at android.os.Binder.transact(Binder.java:361)
        at xz.a(:com.google.android.gms.DynamiteModulesB:82)
        at maps.ad.u$5.run(Unknown Source)
        at android.os.Handler.handleCallback(Handler.java:808)
        at android.os.Handler.dispatchMessage(Handler.java:103)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:5333)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:828)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:644)
        at dalvik.system.NativeStart.main(Native Method)

Thanks for the help! :)

Java Solutions


Solution 1 - Java

I see some options... Not sure which one can fix your issue.

Simpliest

SupportMapFragment extends class android.support.v4.app.Fragment. This way, it has a method getView()

Snackbar.make(mapFragment.getView(), "Click the pin for more options", Snackbar.LENGTH_LONG).show();

Find Root View

From this answer, there's a way to get the root view via:

getWindow().getDecorView().getRootView()

So, maybe, you can do:

Snackbar.make(getWindow().getDecorView().getRootView(), "Click the pin for more options", Snackbar.LENGTH_LONG).show();

NOTE: This approach has the side effect mentioned in the comments below:

> The message will be shown behind the bottom navigation bar if the following method will be used to get view getWindow().getDecorView().getRootView()

Add a dummy LinearLayout to get the View

Honestly, I'm not sure if this solution is possible. I'm not sure if you can add a LinearLayout above the Maps fragment... I think it is OK but since I never work with Maps API before, I'm not sure.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/dummy_layout_for_snackbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment 
        xmlns:map="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="ca.davesautoservice.davesautoservice.MapsActivity" />
</LinearLayout>

and then:

Snackbar.make(findViewById(R.id.dummy_layout_for_snackbar), "Click the pin for more options", Snackbar.LENGTH_LONG).show();

Solution 2 - Java

Try this in any activity:

snackbar(findViewById(android.R.id.content),"your text")

Solution 3 - Java

As Shahab Rauf points out, getting the view via getDecorView() may put the snackbar behind the navigation bar in the bottom. I use the following code: (Use and extend to your delight)

public class BaseActivity extends AppCompatActivity {

    private View getRootView() {
        final ViewGroup contentViewGroup = (ViewGroup) findViewById(android.R.id.content);
        View rootView = null;

        if(contentViewGroup != null)
            rootView = contentViewGroup.getChildAt(0);

        if(rootView == null)
            rootView = getWindow().getDecorView().getRootView();

        return rootView;
    }

    protected void showSnackBarWithOK(@StringRes int res) {
        final View rootView = getRootView();
        if(rootView != null) {
            final Snackbar snackbar = Snackbar.make(getRootView(), res, Snackbar.LENGTH_INDEFINITE);
            snackbar.setAction(R.string.ok, new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    snackbar.dismiss();
                }
            });

            snackbar.show();
        }
    }

    protected void showSnackBar(@StringRes int res) {
        final View rootView = getRootView();
        if(rootView != null)
            Snackbar.make(rootView, res, Snackbar.LENGTH_LONG).show();
    }
}

Solution 4 - Java

OK, So all above solutions are either too complex or irrelevant.

Simplest solution is to use this function ANYWHERE

fun showSnackBar(message: String?, activity: Activity?) {
    if (null != activity && null != message) {
        Snackbar.make(
            activity.findViewById(android.R.id.content),
            message, Snackbar.LENGTH_SHORT
        ).show()
    }
}

Wanna call this function in Activity?

showSnackBar("Test Message in snackbar", this)

Wanna call this function in Fragment?

showSnackBar("Test Message in snackbar", requireActivity())

Happy Coding!

Solution 5 - Java

The simplest way of doing this is to get the advantage of Extention Function in Kotlin.

Step 1: Create a kotlin file i.e Extensions.kt and paste below code

For Activity:

fun Activity.showSnackBar(msg:String){
    Snackbar.make(this.findViewById(android.R.id.content), msg, Snackbar.LENGTH_SHORT)
        .setAction("Ok"){
        }
        .show()
}

For Fragment:

  fun Fragment.showSnackBar(msg:String){
    Snackbar.make(this.requireActivity().findViewById(android.R.id.content), msg, Snackbar.LENGTH_SHORT)
        .setAction("Ok"){
        }
        .show()
}

Step 2: Call it like below:

showSnackBar("PASS HERE YOUR CUSTOM MESSAGE")

Solution 6 - Java

I would say that findViewByIdis the best approach. As using getWindow().getDecorView().getRootView() will cause the message to be shown behind the bottom navigation bar.

In my case, I didn't need to create a dummy element in XML. I used the following code

Snackbar.make(findViewById(R.id.container),"This will start payment process.", Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();

and my XML file is as follows

<android.support.design.widget.CoordinatorLayout 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".HomePatientActivity">

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/appbar_padding_top"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_weight="1"
        android:background="?attr/colorPrimary"
        app:layout_scrollFlags="scroll|enterAlways"
        app:popupTheme="@style/AppTheme.PopupOverlay">

    </android.support.v7.widget.Toolbar>

    <android.support.design.widget.TabLayout
        android:id="@+id/sliding_tabs"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        app:layout_anchorGravity="top"
        app:tabMode="fixed" />

</android.support.design.widget.AppBarLayout>

<android.support.v4.view.ViewPager
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

</android.support.v4.view.ViewPager>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="end|bottom"
    android:layout_margin="@dimen/fab_margin"
    app:srcCompat="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

Solution 7 - Java

Add any layout and assign the layout an id example below:

<RelativeLayout
    android:id="@+id/relativeLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/map_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        />
    
</RelativeLayout>

In your activity find the layout using findViewById:

RelativeLayout relativeLayout = findViewById(R.id.relativeLayout);

Example for other layouts respectively as long as your assign the layouts their Id's:

LinearLayout relativeLayout = findViewById(R.id.linearLayout);
FrameLayout frameLayout = findViewById(R.id.frameLayout);

Displaying the Snackbar:

Snackbar.make(relativeLayout,"You are displaying a Snackbar",Snackbar.LENGTH_SHORT).show();

For other layouts just change the name of the layouts examples:

Snackbar.make(linearLayout,"You are displaying a Snackbar",Snackbar.LENGTH_SHORT).show();

Snackbar.make(frameLayout,"You are displaying a Snackbar",Snackbar.LENGTH_SHORT).show();

Solution 8 - Java

Don't do it.

Why ?

The entire idea of giving an anchor view to snackbar is probably because it gives us the ablity to put it on our favourite place on the screen,

As mentioned in other answers there are ways by which you can attach the root view to it automatically, Using getWindow().getDecorView().getRootView() is one of these, but it can cause this message to appear on bottom navigation view, which doesn't look good (at least to me).

If you want something for general message display go for toast, it is much less error prone and have a consistent appearence, if you need something with interaction I would suggest Alert Dialogue

Solution 9 - Java

Use as extension function:

fun Activity?.showSnackBar(message: String) {
    this ?: return
    Snackbar.make(
        this.findViewById(android.R.id.content),
        message, Snackbar.LENGTH_LONG
    ).show()
}

Solution 10 - Java

With Jetpack Compose stable version released,now you no more need views in xml .

 @Composable
fun Snackbar() {
    Row {

        if (viewModel.snackbarVisibleState.value) {
            Snackbar(
                action = {
                    Button(onClick = {
                        viewModel.snackbarText.value = ""
                        viewModel.snackbarVisibleState.value = false
                    }) {
                        Text("Ok")
                    }
                },
                modifier = Modifier
                    .padding(8.dp)
                    .align(Alignment.Bottom)
            ) {
                Text(viewModel.snackbarText.value)
                
            }
        }
    }
}

Here snackbarVisibleState is a MutableState inside viewmodel whose value you need to change in order to show and dismiss Snackbar.

For more code snippets for Jetpack Compose, refer this - https://androidlearnersite.wordpress.com/2021/08/03/jetpack-compose-1-0-0-sample-codes/

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKelvin KellnerView Question on Stackoverflow
Solution 1 - JavaW0rmH0leView Answer on Stackoverflow
Solution 2 - JavaUmasankarView Answer on Stackoverflow
Solution 3 - JavamillibitView Answer on Stackoverflow
Solution 4 - JavaKishan SolankiView Answer on Stackoverflow
Solution 5 - JavaSuraj BahadurView Answer on Stackoverflow
Solution 6 - JavaRami AlloushView Answer on Stackoverflow
Solution 7 - JavaWinceleeView Answer on Stackoverflow
Solution 8 - Javahumble_wolfView Answer on Stackoverflow
Solution 9 - JavaSumit ShuklaView Answer on Stackoverflow
Solution 10 - JavaAndroid DeveloperView Answer on Stackoverflow