EventBus is an Android library implementing the Publisher-Subscriber pattern via an event bus, allowing data exchange between components without direct dependencies. Developed by GreenRobot, the library simplifies communication between Activity, Fragment, Service, and Background Thread. According to GitHub data (2025), EventBus has over 25 thousand stars and is used in thousands of Android applications. The main operations are subscribe (subscribing to an event), post (sending an event), and sticky event (deferred event for new subscribers).
Key Takeaways
EventBus is an event bus library for Android implementing the Publisher-Subscriber pattern. It allows passing events between application components (Activity, Fragment, Service, ViewModel) without creating explicit dependencies between them. Unlike standard Android mechanisms (Intent, BroadcastReceiver), EventBus works within the process and does not use IPC. The library is optimized for performance and does not use reflection when the Subscriber Index is properly configured.
The EventBus architecture consists of three key elements: Event (POJO class with data), Subscriber (an object with methods annotated with @Subscribe), and EventBus (central dispatcher). The subscriber registers via EventBus.getDefault().register(this) and unregisters via unregister(this). Events are typed: handlers subscribe to a specific event class and are only invoked when an event of that class or its subclasses is posted.
// POJO event
data class MessageEvent(
val message: String,
val timestamp: Long = System.currentTimeMillis()
)
// Subscriber in Activity
class MainActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: MessageEvent) {
textView.text = event.message
}
}
// Sending event from another component
EventBus.getDefault().post(MessageEvent("Hello from Service"))
By default, EventBus uses reflection to find @Subscribe methods during register(). Subscriber Index generates a handler index at compile time using an annotation processor. This eliminates reflection overhead and speeds up registration. To enable it, add eventbus-annotation-processor to build.gradle. EventBus automatically uses the index if it is available in the classpath. Without the index, the library still works but with a slight performance decrease.
When EventBus.getDefault().post(event) is called, the library determines the event type, finds all registered subscribers with @Subscribe methods accepting that type, and invokes them according to the specified ThreadMode. Subscriber lookup is performed using a Class → CopyOnWriteArrayList
A subscriber should register in onStart() and unregister in onStop(). If you register in onCreate() and unregister in onDestroy(), an Activity destroyed without calling onDestroy (due to finish()) may remain in the subscriber list. Subscriber leak is one of the main EventBus issues: an Activity remaining in the subscriber list will not be garbage collected until it unregisters. Always pair register/unregister in the correct lifecycle methods.
The @Subscribe annotation supports a priority parameter (integer, default 0). Handlers with higher priority are called first. cancelEventDelivery() allows interrupting event delivery to remaining subscribers. This is useful for priority handlers (logging, authentication) that can cancel event processing by downstream subscribers. This function is only available in the event posting thread.
// Complex example with priority
data class NavigationEvent(val screen: String, val data: Bundle)
class NavigationInterceptor {
@Subscribe(priority = 10, threadMode = ThreadMode.POSTING)
fun onNavigationEvent(event: NavigationEvent) {
if (event.screen == "restricted" && !isAuthorized) {
EventBus.getDefault().cancelEventDelivery(event)
}
}
}
class AnalyticsLogger {
@Subscribe(priority = 5)
fun logNavigation(event: NavigationEvent) {
analytics.logScreen(event.screen)
}
}
// Sending event
EventBus.getDefault().post(NavigationEvent("profile", bundle))
Android offers several mechanisms for in-process communication: EventBus, LocalBroadcastManager (deprecated), and LiveData/Flow. Each has its advantages and disadvantages. The choice depends on the architectural approach and performance requirements. Modern Google recommendations lean toward LiveData and Flow due to Lifecycle integration and the absence of leaks.
| Feature | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| Typing | Via event class | Via Intent filter (String) | Via generic type |
| Lifecycle-aware | No (manual unregister) | No (manual unregister) | Yes (automatic) |
| Sticky | Yes (postSticky) | No | Yes (LiveData is always sticky) |
| ThreadMode | MAIN, POSTING, BACKGROUND, ASYNC | Main only | Via observe/observeOn |
| Performance | High (Subscriber Index) | Medium (IPC wrapper) | High (observation) |
EventBus is useful in projects with significant legacy code and where LiveData/Flow are not available (Java-only projects). EventBus sticky events provide flexibility absent in LocalBroadcastManager. EventBus is also easier for sending events from Service to Activity without ViewModel — especially when you need to notify about background task progress. The library has a minimal size (about 50 KB) and does not add dependencies.
LiveData and Flow are part of Android Jetpack and integrated with Lifecycle. They automatically unsubscribe when a component is destroyed, eliminating memory leaks. Flow supports coroutines and complex transformation operators. Google recommends LiveData for the UI layer and Flow for repositories. EventBus remains useful for cross-module events where navigation and business logic do not fit into MVVM.
Subscribe is registering an event handler via the @Subscribe annotation. The method must be public, void, and accept exactly one parameter — the event type. Post is sending an event to all subscribed handlers via EventBus.getDefault().post(event). The post method does not return a result and does not indicate how many handlers were invoked. For events with a response, use a separate Event class with a result field.
An event is any Java/Kotlin class. It is recommended to use data class for immutable events and a regular class for events with mutable fields. Event naming should reflect the action: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. Avoid a single generic Event class with a String type field — this eliminates the benefits of typing. An event hierarchy (parent Event) allows subscribing to a group of related events.
// Event hierarchy
open class UserEvent
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val reason: String) : UserEvent()
// Subscribing to base class
class SessionManager {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUserEvent(event: UserEvent) {
when (event) {
is UserLoggedIn -> startSession(event.userId)
is UserLoggedOut -> endSession(event.reason)
}
}
}
// Sending
EventBus.getDefault().post(UserLoggedIn("user_123"))
Calling EventBus.getDefault().register(this) scans the subscriber class via reflection or Subscriber Index and stores the found @Subscribe methods in the event map. Unregister removes the subscriber from the map. Re-registration without unregistering is an error (will throw MultipleSubscriberException). For Fragment, register in onStart() and unregister in onStop(). For Service, in onCreate() and onDestroy(). For ViewModel, it is not recommended — use LiveData instead.
A sticky event is an event that persists in EventBus after being sent. New subscribers registered after postSticky() immediately receive the last sticky event of the corresponding type. This is convenient for passing initial state: when opening a screen, it receives the latest data sent before its registration. You can remove a sticky event via EventBus.getDefault().removeStickyEvent(Class).
ThreadMode determines which thread executes the handler. POSTING (default) — the handler runs in the same thread where post was called. MAIN — the handler runs on the main thread via Handler. BACKGROUND — the handler runs on a background thread; if post was called on the main thread, EventBus queues the handler on a background thread. ASYNC — each handler runs on a separate background thread from a thread pool. For UI updates, use MAIN.
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)
// Sending sticky event from LocationService
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))
// Subscriber receives last location immediately after registration
class MapFragment : Fragment() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
// Will immediately receive LocationEvent if postSticky was called
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onLocationEvent(event: LocationEvent) {
moveMapTo(event.lat, event.lng)
}
}
// Removing sticky event
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)
BACKGROUND uses a single background thread for all handlers — they execute sequentially. ASYNC creates a new thread from the pool for each handler — they execute in parallel. BACKGROUND is suitable for I/O operations with a shared database. ASYNC is for independent long-running operations (network requests). Both modes require thread-safe access to shared resources. Keep the thread count in mind: the ASYNC pool is unlimited.
When using EventBus, developers often make mistakes leading to memory leaks, unexpected invocations, and performance degradation. Most critical: forgotten unregister in Activity, registration in onCreate (rather than onStart/onStop), subscribing to Object (all events), sending events in an infinite loop. Profiling with Android Profiler helps identify issues.
The most common mistake is registering an Activity in onCreate() without unregistering in onDestroy(). Result: EventBus holds a reference to the Activity, GC cannot free it. When the screen is rotated, a new Activity is created while the previous one remains in memory. Solution: always pair register/unregister in onStart/onStop. For Fragment, use the same pattern. If an Activity is held by EventBus after finish, check via Memory Profiler.
Without Subscriber Index, EventBus uses reflection to find @Subscribe methods on each register(). On Android 6-7 devices, reflection is slow, causing delays up to 50 ms. Subscriber Index eliminates reflection entirely: methods are indexed at compile time via an annotation processor. For projects with 20+ subscribers, the index is mandatory. Ensure kapt or annotationProcessor is configured in build.gradle.
// build.gradle (app) — adding Subscriber Index
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// For Kotlin use kapt
plugins {
id 'kotlin-kapt'
}
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// Index configuration (in defaultConfig)
kapt {
arguments {
arg('eventBusIndex', 'com.app.EventBusIndex')
}
}
Modern projects using Kotlin and Jetpack Compose prefer SharedFlow and Channel from the kotlinx.coroutines library. SharedFlow supports replay (sticky), buffering, and backpressure. Channel handles one-shot events (toast, navigation). Both solutions are integrated with Lifecycle via repeatOnLifecycle and do not require manual unsubscription. For new projects, SharedFlow is recommended over EventBus. For existing projects, migration is justified during refactoring.
Frequently Asked Questions
EventBus is an event bus for data exchange between any components (Activity, Fragment, Service). LiveData is a lifecycle-aware wrapper for data observed by a UI component. LiveData automatically manages subscription through Lifecycle. EventBus requires manual register/unregister. LiveData is recommended for the UI layer, EventBus for cross-module communication where LiveData is inconvenient.
A sticky event is an event that persists in EventBus after being sent. New subscribers registered after postSticky() immediately receive the last sticky event. It is used for initial state: when opening a screen, it receives the latest data without a new request. It is removed via removeStickyEvent() or when a new sticky event of the same type is sent.
Yes, EventBus is thread-safe. Calling post() is possible from any thread. Event delivery to subscribers is synchronized internally. ThreadMode determines the handler execution thread: MAIN (main thread via Handler), POSTING (caller thread), BACKGROUND (background task queue), ASYNC (separate thread). For UI updates, use MAIN. For heavy operations, use ASYNC.
Enable logging via EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install(). Subscribe to NoSubscriberEvent to track events without handlers. Use SubscriberExceptionEvent for global exception handling. Android Profiler helps find leaks. For complex scenarios, write a test: EventBus.getDefault().register(mock) + post(event) + verify(mock).
No, EventBus (GreenRobot) is tied to Android SDK and JVM. For Kotlin Multiplatform, use Kotlin Multiplatform SharedFlow or KMMBus — libraries supporting shared code. EventBus works on the Android side of a KMM project but is not available in commonMain. For cross-platform events, prefer native platform mechanisms or abstraction via expect/actual.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also