📖 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.

Szybki przykład
// 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.

Wysyłanie eventó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.

Subskrypcja 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.

Sticky events
// 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
Użycie predefiniowanych eventów
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

Activity ↔ 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

Synchronizacja koszyka
// 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

Premium status
// 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