Creating Generic Standalone Bottom Sheets in Android

Aditya
DSC KIET
Published in
8 min readJun 11, 2021

Most of you would agree on this when we say that we generally work with Bottom Sheets by attaching them to a Fragment or an Activity. And by “attaching them”, we mean that these Bottom Sheets are bind to the ViewModel of the parent Activity.

But what if we want to create a wholly independent and reusable Bottom Sheet of our own? One that has its own Repository and own ViewModel so that we can use it wherever we want inside our app. Sounds interesting? Let’s deep dive into this two-part article series to know how we can do this! 😃

In this part, we will be setting up a base for our Bottom Sheet, and use Dagger to inject View Model into it. In the second part, we’ll be discussing some difficulties that we may face while sharing data in and out of such a Bottom sheet, and how we can tackle such a problem.

Prerequisites

Before we start off, I hope that you are already familiar with the following concepts/libraries :

Approach

If you would have worked with Dagger before, you’d know that Dagger provides some of its own Classes that we can extend to create one of our own. To name a few, we usually extend our Activity from DaggerAppCompatActivity and Fragment from DaggerFragment. If you explore a little bit more, you will find that there isn’t any such class as “DaggerBottomSheet” from which we can extend our own Bottom Sheets.

So, why not create one for ourselves? The core idea behind developing such a Bottom Sheet is to create a Base Bottom Sheet which is going to be an abstract class, in which we are going to inject our ViewModel. 😉

Before we get into the main stuff, let me walk you through what already exists in our codebase :

  • A BaseApplication class which extends DaggerApplication
  • An AppComponent class which is the heart of the DI package.

The following modules are part of our DI package. We use @Component annotation to bind these into the AppComponent class.

  • AppModule (binds context for my application)
  • ActivityBindingModule (binds all the Activity classes for my application)
  • ViewModelModule (binds all the ViewModels of my application)
  • RepositoryModule (binds all the Repositories of my application)
  • AndroidInjectionModule & AndroidSupportInjectionModule contain the bindings to ensure the usability of Dagger framework classes in our project.

The above code snippet shows the structure of our AppComponent.java class.

Let’s dive into the main stuff now :)

Implementation

To start off, let us first create an empty repository. Whenever I have to create a repository, I prefer to create an Interface and then implement the same into a separate class so as to maintain Data Layer Abstraction in my project.

The next step is to create a ViewModel for our BottomSheet and inject our Repository into our ViewModel using Constructor Injection.

👉 For those of you who did not get the @Inject part here, our BottomSheetRepository gets ‘provided’ to us because we created its Non-Nullable instance in the Repository Module.

And now, the time has come to develop our own Bottom Sheet as promised! As said before, we’ll start by creating an abstract class so that whenever we have to create a new Bottom Sheet, we’d just need to extend this class.

Let’s now go through this code snippet step by step. 😮

We hope you’d agree when we say, that a bottom sheet is nothing but a Fragment! And in case you do not seem to agree, try going through the inheritance tree of a BottomSheetDialogFragment (it extends AppCompatDialogFragment followed by a DialogFragment and finally a Fragment). This is what made me develop the code shown above. If you try going through the implementation of DaggerFragment , you will find code that is very similar to the one shown in the above snippet.

So yeah! Here we are. What’s going on here?

  • We extend our abstract class with BottomSheetDialogFragment and implement it using HasAndroidInjector Interface!
  • The @Inject DispatchingAndroidInjector performs member injection of basic layout types in our app (Activity and Fragments).
  • An object of this type returns an AndroidInjector which is used in the inject() method in AndroidSupportInjection class.
  • And finally, when we launch our bottom sheet using this class, we use the static inject() method from AndroidSupportInjection class in our onAttach() method to tell dagger that, Hey! I’ll provide you the AndroidInjector object, let other classes inject into me (this: Context).

Other than this, we pass a generic type V which extends ViewModel into our Base Bottom Sheet’s type parameter. Using this, we can inject whatever ViewModel we need to. Now whenever we create a new Bottom Sheet using this class, the getViewModel() method is overridden in our child class which is used to provide an instance of the ViewModel we want to use with our Bottom Sheet!

So this was all about how we can use dagger to create our own DaggerBottomSheet! Let us now move one step closer to our goal — Making our Bottom Sheet as Generic as possible.

Now we could require bottom sheets of varying heights. In some situations, we just need the layout to wrap whatever the content is present inside, but in some other situations, we may also need the bottom sheet’s top to touch the top of the device’s screen (we can call it a Full Height Bottom Sheet as well). So, to achieve this variance, we first create a utility function that can be used to set this height as desired.

To give you a ‘tip-of-the-iceberg from the above snippet, we are just trying to use a combination of LayoutPrams (to set the height of the bottom sheet), DisplayMetrics (one of whose methods returns the screen height of the physical device you are using), and the BottomSheetBehaviour, using which we set the state of our Bottom Sheet.

Next, we make use of this utility function to modify our DaggerBottomSheet so that it becomes easy to set up the height of any child Bottom Sheets we create.

Here, we override the method onCreateDialog() from the DialogFragment class, create an instance of BottomSheetDialog class and link it to the setupFullHeight() method we created in the Utils class. fullHeightFlag is a boolean value that is always passed from the onAttach() method of the child Bottom Sheet we create.

Try guessing what would happen if we set the flag from onCreateView() or onViewCreated() methods !!

The End Result: If you pass false in the setUpFullHeightBottomSheet() method, our Bottom Sheet rises up to the minimum height attained by the layout using ‘wrap_content’. Otherwise, our bottom sheet rises up to the top of the screen, irrespective of our layout’s actual height!

Let us now see what a new child BottomSheet created using our Base with a typical Data binding setup, looks like.

All seems good, right? It isn’t! 😛 The main problem we are going to face in an independent Bottom Sheet is data transfer. How do you go about sending and receiving data in and out of this bottom sheet? If it was attached to an activity or fragment (as we do usually), it would already be linked to a ViewModel, so data transfer operations would have been a cakewalk.

So, to get to a working solution where you can send and receive data, in and out of your bottom sheet with ease, we’ll be using an external library which is commonly known as EventBus.

Working with Event Bus

An EventBus is like an internal Bus that listens to events, and also delivers data to the destinations that have subscribed to this Bus. According to official docs,

EventBus is an open-source library for Android and Java using the publisher/subscriber pattern for loose coupling. EventBus enables central communication to decoupled classes with just a few lines of code — simplifying the code, removing dependencies, and speeding up app development.

So, let us now see how we can integrate an event bus into our app, so as to ease out data transfer operations for our bottom sheet.

Firstly, start off by adding the following dependency to your app level Gradle file followed by a Gradle Sync (note that at the time of writing this article, EventBus is on v3.2.0):

Now there are mainly two situations:

  • You have a source fragment/activity from where you are launching your bottom sheet to send data into it.
  • You have a destination fragment/activity where you want to deliver some data back from your bottom sheet.

The approach for both of these cases is going to be almost the same with a little catch in the former one. 😉

Let’s discuss the former part first. To start off, head on to DaggerBottomSheet we developed previously, and add the following lines of code inside your DaggerBottomSheet class:

Here, firstly we create a generic method setDataOnEventBus(T t) that is used to set the data on our EventBus. We have kept the parameter of data to be passed as a generic type so that we can use the same method in multiple instances, whether we want to pass just a String value, or maybe a whole Class (or Model class) as data.

Now in the onStart() and onStop() methods, we need to register and unregister from the EventBus so that we can have access to the Bus only during the life cycle of our bottom sheet.

Next, we head on to the MyBottomSheet we developed, where we add a subscriber method for our EventBus so that whenever some data is put on the Bus, we can receive it via this method. To do this, we add the following lines of code inside our MyBottomSheet class:

Here, we create a method onMessageEvent() where we receive the data into our Bottom Sheet. I have also created a MutableLiveData object in our ViewModel as shown on Line 9 in the above snippet so that we can set the value when received.

So now, we are almost done setting up our EventBus to get data inside our bottom sheet. Now the only thing we need to do is, send the data! We do this by writing the following lines of code, from the source fragment/activity:

If you have come this long, you must be already familiar with the first 3 lines of code :) Let’s talk about the remaining ones now.

Here we use EventBus’s post() method to send an object of type string to our Bottom Sheet.

Notice that we are calling the EventBus from inside a Handler, and not directly. Can you guess why we are doing so? 😏

So that’s it, guys. This way we can now get data inside your bottom sheet from wherever we want. Now let’s quickly talk about the vice-versa situation.

When we want to send data back from our bottom sheet, we can use the generic method setDataOnEventBus(T t) we created in our DaggerBottomSheet to set the data on the event bus.

To answer the above question as well, we use a Handler when we are sending data to our bottom sheet because if we try to send it directly, then at that point of time, our Bottom sheet has not been able to register to listen to the event bus and hence, it misses out on the sent data. We have added it inside a Handler along with a 500ms delay to cover up for this situation. 😃

We don’t need to use a Handler when sending data back from the Bottom Sheet because the parent activity’s life cycle isn’t destroyed when our Bottom Sheet is overlayed.

You may also head on to Event Bus — Github to read the official documentation of Event Bus and its workflow.

So here we are! Nearing the end of this article. We first discussed how we can create a generic bottom sheet using Dagger’s injection methods and then we talked about data transfer operations using an event bus. To the best of my knowledge, Event Bus is one of the best libraries I’ve used during native app development! Let’s wrap this up, guys. Keep on coding, keep up your thirst for knowledge 😋

Until the next blog, goodbye!

--

--