EventBus
Lekki system eventów oparty na Kotlin Flow
📖 Przegląd
Moduł EventBus to lekki system eventów oparty na Kotlin Flow.
Umożliwia komunikację między komponentami aplikacji bez bezpośredniego powiązania.
// Definicja eventu
data class UserLoggedIn(val userId: String) : EventBus.Event
// Wysyłanie
ADict.EventBus.post(UserLoggedIn("user123"))
// Odbieranie (lifecycle-aware)
ADict.EventBus.on<UserLoggedIn>(lifecycleOwner) { event ->
showWelcome(event.userId)
}
📤 Wysyłanie eventów
post(event: Event)
Wyślij event asynchronicznie do wszystkich subskrybentów.
postSync(event: Event)
Wyślij event synchronicznie (blokuje do emisji).
postSticky(event: Event)
Wyślij sticky event - zachowuje ostatnią wartość dla nowych subskrybentów.
// Definicje eventów
data class UserLoggedIn(val userId: String, val name: String) : EventBus.Event
data class UserLoggedOut(val reason: String? = null) : EventBus.Event
data class CartUpdated(val itemCount: Int, val total: Double) : EventBus.Event
data class AppConfig(val theme: String, val language: String) : EventBus.Event
// Asynchroniczne wysłanie
ADict.EventBus.post(UserLoggedIn("123", "Jan"))
// Synchroniczne (rzadko potrzebne)
ADict.EventBus.postSync(CartUpdated(5, 99.99))
// Sticky - nowi subskrybenci od razu dostaną wartość
ADict.EventBus.postSticky(AppConfig(theme = "dark", language = "pl"))
📥 Subskrypcja eventów
Lifecycle-aware (zalecane)
on<T : Event>(lifecycleOwner, callback): String
Subskrybuj eventy z automatycznym anulowaniem przy zniszczeniu lifecycle.
Zwraca: ID subskrypcji
onAny(lifecycleOwner, callback): String
Subskrybuj wszystkie eventy.
once<T : Event>(lifecycleOwner, callback)
Subskrybuj tylko jeden event, potem automatycznie anuluj.
Flow-based (dla ViewModel/Coroutines)
eventsOf<T : Event>(): SharedFlow<T>
Pobierz Flow eventów określonego typu.
eventsFlow(): SharedFlow<Event>
Pobierz Flow wszystkich eventów.
// W Activity/Fragment (lifecycle-aware)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Subskrypcja konkretnego typu
ADict.EventBus.on<UserLoggedIn>(this) { event ->
showWelcome(event.name)
updateUI(event.userId)
}
ADict.EventBus.on<CartUpdated>(this) { event ->
updateCartBadge(event.itemCount)
}
// Subskrypcja wszystkich eventów
ADict.EventBus.onAny(this) { event ->
Log.d("EventBus", "Received: ${event::class.simpleName}")
}
// Tylko jeden event
ADict.EventBus.once<UserLoggedIn>(this) { event ->
showFirstLoginBonus()
}
}
}
// W ViewModel (Flow-based)
class MainViewModel : ViewModel() {
init {
viewModelScope.launch {
ADict.EventBus.eventsOf<UserLoggedIn>().collect { event ->
// Obsługa
}
}
viewModelScope.launch {
ADict.EventBus.eventsFlow().collect { event ->
when (event) {
is UserLoggedIn -> handleLogin(event)
is UserLoggedOut -> handleLogout(event)
}
}
}
}
}
Manualna anulacja
unsubscribe(subscriptionId: String)
Ręczne anulowanie subskrypcji.
unsubscribeAll()
Anuluj wszystkie subskrypcje.
📌 Sticky events
Sticky events zachowują ostatnią wartość i natychmiast ją dostarczają nowym subskrybentom. Idealne dla stanu aplikacji (theme, config, user session).
postSticky(event: Event)
Wyślij sticky event.
onSticky<T : Event>(lifecycleOwner, callback): String
Subskrybuj sticky event - otrzymasz ostatnią wartość natychmiast + przyszłe.
getStickyEvent<T : Event>(): T?
Pobierz aktualny sticky event (lub null).
removeStickyEvent<T : Event>()
Usuń sticky event danego typu.
clearStickyEvents()
Wyczyść wszystkie sticky eventy.
// Definicja sticky eventu
data class AppConfig(
val theme: String,
val language: String,
val isPremium: Boolean
) : EventBus.Event
// Wysłanie sticky
ADict.EventBus.postSticky(AppConfig(
theme = "dark",
language = "pl",
isPremium = true
))
// Subskrypcja - od razu dostanie aktualną wartość
ADict.EventBus.onSticky<AppConfig>(this) { config ->
applyTheme(config.theme)
setLanguage(config.language)
}
// Pobranie bez subskrypcji
val currentConfig = ADict.EventBus.getStickyEvent<AppConfig>()
currentConfig?.let {
if (it.isPremium) showPremiumFeatures()
}
// Aktualizacja (po prostu wyślij nowy)
val newConfig = currentConfig?.copy(theme = "light") ?: return
ADict.EventBus.postSticky(newConfig)
📦 Predefiniowane eventy
EventBus zawiera zestaw gotowych eventów w EventBus.Events:
| Event | Opis | Właściwości |
|---|---|---|
ConfigUpdated |
Konfiguracja zaktualizowana | key, value |
PurchaseCompleted |
Zakup zakończony | productId, success |
AdShown |
Reklama wyświetlona | adType, zone |
AdClosed |
Reklama zamknięta | adType, zone |
RewardEarned |
Nagroda otrzymana | type, amount |
PremiumStatusChanged |
Zmiana statusu premium | isPremium |
ConsentChanged |
Zmiana zgód GDPR | canShowAds |
Custom |
Generyczny event | name, data |
import rip.nerd.adictlibrary.modules.EventBus.Events.*
// Wysyłanie
ADict.EventBus.post(PurchaseCompleted("premium_monthly", true))
ADict.EventBus.post(AdShown("interstitial", "level_complete"))
ADict.EventBus.post(RewardEarned("coins", 100))
ADict.EventBus.postSticky(PremiumStatusChanged(true))
// Custom event z danymi
ADict.EventBus.post(Custom("custom_action", mapOf(
"source" to "home_screen",
"user_id" to "123"
)))
// Odbieranie
ADict.EventBus.on<PurchaseCompleted>(this) { event ->
if (event.success) {
showThankYou(event.productId)
}
}
ADict.EventBus.on<RewardEarned>(this) { event ->
addReward(event.type, event.amount)
}
ADict.EventBus.onSticky<PremiumStatusChanged>(this) { event ->
updatePremiumUI(event.isPremium)
}
💡 Przykłady praktyczne
Komunikacja między Activity a Fragment
// Event
data class FilterChanged(
val category: String?,
val priceRange: IntRange?,
val sortBy: String
) : EventBus.Event
// Fragment wysyła event
class FilterFragment : Fragment() {
fun onApplyFilters() {
ADict.EventBus.post(FilterChanged(
category = selectedCategory,
priceRange = priceRange,
sortBy = sortOrder
))
dismiss()
}
}
// Activity odbiera
class ProductListActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ADict.EventBus.on<FilterChanged>(this) { filters ->
viewModel.applyFilters(filters)
}
}
}
Synchronizacja stanu między ekranami
// Sticky event dla stanu koszyka
data class CartState(
val items: List<CartItem>,
val total: Double,
val itemCount: Int
) : EventBus.Event
// CartManager aktualizuje stan
object CartManager {
private var items = mutableListOf<CartItem>()
fun addItem(product: Product, quantity: Int) {
items.add(CartItem(product, quantity))
notifyStateChanged()
}
fun removeItem(productId: String) {
items.removeAll { it.product.id == productId }
notifyStateChanged()
}
private fun notifyStateChanged() {
ADict.EventBus.postSticky(CartState(
items = items.toList(),
total = items.sumOf { it.totalPrice },
itemCount = items.sumOf { it.quantity }
))
}
}
// Każdy ekran subskrybuje
class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ADict.EventBus.onSticky<CartState>(viewLifecycleOwner) { state ->
cartBadge.text = state.itemCount.toString()
cartBadge.isVisible = state.itemCount > 0
}
}
}
class ProductDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ADict.EventBus.onSticky<CartState>(this) { state ->
updateAddToCartButton(state)
}
}
}
Obsługa premium/reklam
// Po zakupie premium
fun onPremiumPurchased() {
ADict.EventBus.postSticky(EventBus.Events.PremiumStatusChanged(true))
}
// Wszystkie miejsca z reklamami reagują
class AdBannerView : View {
init {
ADict.EventBus.onSticky<EventBus.Events.PremiumStatusChanged>(
findViewTreeLifecycleOwner()!!
) { event ->
visibility = if (event.isPremium) View.GONE else View.VISIBLE
}
}
}
class InterstitialManager {
fun showIfAllowed() {
val isPremium = ADict.EventBus
.getStickyEvent<EventBus.Events.PremiumStatusChanged>()
?.isPremium ?: false
if (!isPremium) {
showInterstitial()
}
}
}
📚 API Reference
| Metoda | Opis |
|---|---|
post(event) |
Wyślij event asynchronicznie |
postSync(event) |
Wyślij event synchronicznie |
postSticky(event) |
Wyślij sticky event |
on<T>(lifecycleOwner, callback) |
Subskrybuj event (lifecycle-aware) |
onAny(lifecycleOwner, callback) |
Subskrybuj wszystkie eventy |
onSticky<T>(lifecycleOwner, callback) |
Subskrybuj sticky event |
once<T>(lifecycleOwner, callback) |
Subskrybuj tylko jeden event |
eventsOf<T>() |
Pobierz Flow eventów typu T |
eventsFlow() |
Pobierz Flow wszystkich eventów |
getStickyEvent<T>() |
Pobierz aktualny sticky event |
removeStickyEvent<T>() |
Usuń sticky event |
unsubscribe(subscriptionId) |
Anuluj subskrypcję |
unsubscribeAll() |
Anuluj wszystkie subskrypcje |
clearStickyEvents() |
Wyczyść sticky eventy |
reset() |
Resetuj wszystko |