Overview

FyreMemories is an Android memory SDK that adds a photo and video memories experience to your app. It gives you three surfaces: a thumbnail carousel, a swipeable full-screen feed, and a headless repository for building your own UI. The SDK handles the player, caching, analytics, and per-user data isolation.

1
Add the dependency

One line in build.gradle.kts
implementation("com.fyrememories:fyre-memories-sdk:1.0.1").

2
Initialize after sign-in

Call FyreMemories.init() once a Firebase user is signed in.

3
Show the carousel or open the feed

Drop MemoriesCarouselView into any layout, or call FyreMemories.openFeed().

Everything funnels through one entry point: FyreMemories. Everything beyond step 3 is optional.

SurfaceClassUse it for
CarouselMemoriesCarouselViewA horizontal thumbnail strip you embed in your own screens.
Full-screen feedMemoriesFeedActivityA swipeable, auto-playing video player with like, save, share, and download.
HeadlessMemoriesRepositoryFetch memories yourself and render a custom UI.
Installation

The SDK is published to Maven Central as com.fyrememories:fyre-memories-sdk. Make sure your repositories include it.

settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}
build.gradle.kts
dependencies {
    implementation("com.fyrememories:fyre-memories-sdk:1.0.1")
}
â„šī¸
Transitive dependencies are includedThe SDK pulls in Kotlin coroutines, kotlinx-serialization, OkHttp, Room, Media3/ExoPlayer, Material/AppCompat, and Coil. You don't add those yourself. If your app also uses them, keep the versions compatible.
Module configuration

The SDK targets Java 17. Your module must compile against a matching toolchain.

app/build.gradle.kts
android {
    compileSdk = 35
    defaultConfig { minSdk = 24 }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions { jvmTarget = "17" }
}
💡
FileProvider authorityThe SDK registers a FileProvider with authority {applicationId}.fileprovider for sharing downloads. If your app declares the same authority, rename yours to avoid a manifest-merger conflict.
Permissions

Add INTERNET to your AndroidManifest.xmlif it isn't already present — don't rely on manifest merging from the SDK. If you enable the in-feed Download button, request WRITE_EXTERNAL_STORAGE at runtime on API 28 and below before the download. On API 29 and above the SDK writes to app-scoped storage and no runtime permission is needed.

Initialization

FyreMemories.init()requires a signed-in Firebase user. Where you call it depends on your app's auth flow.

âš ī¸
Don't call init() in Application.onCreate()init() reads FirebaseAuth.getInstance().currentUser. At the point Application.onCreate() runs, Firebase hasn't restored the session yet, so currentUser may still be null even for a returning, signed-in user. If no user is signed in when you call it, init() throws FyreInitializationException. Call it after your splash/auth check, inside a sign-in success callback, or inside an auth state listener — never directly in onCreate().
Most common — user was already signed in on a previous session

On returning launches, Firebase restores the session synchronously. Initialize the SDK right after your splash / auth check, once you confirm the user is signed in:

// In your SplashActivity, MainActivity, or wherever you check auth state
if (FirebaseAuth.getInstance().currentUser != null) {
    FyreMemories.init(
        context = applicationContext,
        config = FyreMemoriesConfig(enableLogging = BuildConfig.DEBUG)
    )
    // proceed to main screen
} else {
    // send user to login screen
}
After a fresh sign-in (Google, email, anonymous, etc.)

Call init() in your sign-in success callback, right after the user is authenticated:

// After signInWithCredential, signInWithEmailAndPassword, signInAnonymously — any provider
firebaseAuth.signIn(...).addOnSuccessListener {
    FyreMemories.init(
        context = applicationContext,
        config = FyreMemoriesConfig(enableLogging = BuildConfig.DEBUG)
    )
    // proceed to main screen
}
Using an auth state listener

If your app manages auth state reactively, register the listener once (e.g. in Application.onCreate()) — the init() call itself only fires inside the callback, once a user is confirmed:

// Register once, e.g. in Application.onCreate()
FirebaseAuth.getInstance().addAuthStateListener { auth ->
    if (auth.currentUser != null && !FyreMemories.isInitialized) {
        FyreMemories.init(context = applicationContext)
    }
}
Wait for init in early coroutines

Calling init() again after success logs a warning and is ignored. To use the repository before init has finished, suspend on awaitInitialized() first.

lifecycleScope.launch {
    FyreMemories.awaitInitialized()
    val userId = FyreMemories.currentUserId() ?: return@launch
    val result = FyreMemories.repository.fetchMemories(userId)
}
âš ī¸
awaitInitialized() caveat across sign-out cyclesThe internal initDeferred is not reset by clearUser(). After sign-out and re-init(), don't rely on awaitInitialized() alone — check FyreMemories.isInitialized, or call init() and only use the SDK after it returns.
â„šī¸
Auto user identityThe SDK resolves the correct identifier automatically: email for authenticated users, UIDfor anonymous. You normally don't call setUser().
💡
No baseUrl neededThe SDK ships a built-in production URL. Only set baseUrl if you point at a custom backend or staging environment. The SDK does not read a host BuildConfig.FYRE_API_URL or any app buildConfigField automatically — pass the URL explicitly in FyreMemoriesConfig if you need one.
Configuration

All FyreMemoriesConfig fields have defaults. Override only what you need.

FyreMemories.init(
    context = this,
    config = FyreMemoriesConfig(
        baseUrl = "",                                  // empty = built-in production URL
        customization = FyreCustomization(/* ... */),  // optional UI overrides
        enableLogging = BuildConfig.DEBUG,
        cacheSizeBytes = 200L * 1024 * 1024,           // video cache (200 MB)
        thumbnailCacheSizeBytes = 50L * 1024 * 1024,   // thumbnail cache (50 MB)
        themeOverlay = R.style.MyFyreOverlay,          // optional theme overlay
        logger = MyTimberLogger(),                     // optional log sink
        connectTimeoutMs = 10_000L,
        readTimeoutMs = 15_000L,
        writeTimeoutMs = 15_000L,
    )
)
FieldDefaultNotes
baseUrl""Backend base URL. Empty uses the built-in production URL. Include the scheme, no trailing slash.
customizationnullFull UI customization tree. null uses defaults.
enableLoggingfalseVerbose SDK logs. Pass BuildConfig.DEBUG so logs stay off in release.
cacheSizeBytes200 MBExoPlayer video disk cache, synced to PlayerOptions at init. No live update*Options setter, but MemoryPlayerCache recreates itself if you pass a different size on re-init.
thumbnailCacheSizeBytes50 MBCoil thumbnail disk cache size.
themeOverlay0Theme overlay applied to all SDK views and the feed activity. 0 means none.
loggernullForward SDK logs to Timber or Crashlytics. Only called when enableLogging is true.
connectTimeoutMs10 000OkHttp connect timeout.
readTimeoutMs15 000OkHttp read timeout.
writeTimeoutMs15 000OkHttp write timeout.
User Management

Each user ID gets isolated in-memory state and an on-disk cache. One user's memories are never shown to another, which keeps shared devices and account switching safe.

Read the current user
val userId: String? = FyreMemories.currentUserId()
// email for authenticated users, uid for anonymous, null if signed out
Switch users (custom auth)

Call setUser()only for a custom auth flow or to override the auto-detected ID. It activates that user's state and auto-fetches their memories when the ID changes. The SDK keeps the 3 most-recently-used users in memory (LRU).

FyreMemories.setUser(newUserId)
Mark a memory viewed

The feed marks views automatically. For custom UIs, update the seen or unseen ring state yourself.

FyreMemories.markAsViewed(memory.id)
Sign-out

Call clearUser() on sign-out. It:

  • Purges the current user's repository memory and disk cache (fyre_memories_cache_{userId}.json)
  • Flushes and clears queued analytics, and shuts down the analytics batcher
  • Clears the global liked/viewed MemoriesPreferences (not scoped per user)
  • Nulls the internal service locator and resets isInitialized to false, so you can call init() again on the next sign-in
FyreMemories.clearUser()
FirebaseAuth.getInstance().signOut()
Full-Screen Feed

The easiest way to open the swipeable player. If no user is signed in, the call logs a warning and does nothing.

FyreMemories.openFeed(context)                        // start at position 0
FyreMemories.openFeed(context, startPosition = 3)    // jump to a memory
Direct or embedded
MemoriesFeedActivity.start(context, userId, startPosition = 3)
MemoriesFeedActivity.isActive()      // is the feed on screen?
MemoriesFeedActivity.closeCurrent()  // close it programmatically

// Host the fragment inside your own screen
supportFragmentManager.beginTransaction()
    .replace(R.id.container, MemoriesFeedFragment.newInstance(userId, startPosition = 0))
    .commit()
Headless Repository

Access FyreMemories.repository to fetch data and render your own UI. It exposes a reactive, per-user Flow. Accessing it before init() throws IllegalStateException.

val userId = FyreMemories.currentUserId() ?: return

lifecycleScope.launch {
    FyreMemories.repository.fetchMemories(userId)  // updates state and cache
}

lifecycleScope.launch {
    FyreMemories.repository.memories(userId).collect { result ->
        result
            .onSuccess { memories -> renderList(memories) }
            .onFailure { error -> showError(error) }
    }
}
MemberDescription
fetchMemories(userId)Fetches from backend, updates state and cache. Caps at 200 items and deduplicates concurrent fetches per user.
memories(userId)Reactive stream of the latest list for that user, seeded from disk cache.
saveMemory(sourceKey)Saves an existing memory key to the user's account.
markAsViewed(memoryId)Marks a memory viewed in-memory so carousel rings update live.
clearUserCaches(userId?)Clears disk and memory caches for one user, or all if null.
â„šī¸
404 means empty, not an errorWhen the backend has no memories for a user it responds 404. The repository maps this to Success(emptyList()), so an empty feed is a normal success.
Feed Options
FeedOptions(
    showLike = true, showSave = true, showDownload = true,
    showShare = true, showClose = true, showSeekBar = true,
    showPositionText = true,       // "3 / 20" counter

    // Custom icons (@DrawableRes; 0 = SDK default)
    iconLikeOutline = R.drawable.ic_heart_outline,
    iconLikeFilled  = R.drawable.ic_heart_filled,
    iconSave = R.drawable.ic_save, iconDownload = R.drawable.ic_download,
    iconShare = R.drawable.ic_share, iconClose = R.drawable.ic_close,
    iconPlay = R.drawable.ic_play,

    // Colors (ARGB; 0 = theme attr)
    likedTint = Color.RED, unlikedTint = Color.WHITE,
    chromeTint = Color.WHITE, backgroundColor = Color.BLACK,

    // Optional message overrides (null = resource string)
    shareChooserTitle = null, downloadFailedText = null,
    storagePermissionDeniedText = null,

    // Playback
    autoAdvanceOnComplete = true, autoAdvanceDelayMs = 300L,
    loopCurrent = false, tapToPlayPause = true,
)
Live update
FyreMemories.updateFeedOptions { it.copy(showSave = false, likedTint = Color.RED) }
Player Options
PlayerOptions(
    autoPlay = true, loop = false, mutedByDefault = false,
    playbackSpeed = 1.0f,                 // must be > 0
    resizeMode = ResizeMode.FIT,          // FIT | FILL | ZOOM
    prefetchNext = true,                  // pre-buffer the next video
    cacheSizeBytes = 200L * 1024 * 1024,  // 200 MB default; must be >= 0
)
Live update
FyreMemories.updatePlayerOptions { it.copy(mutedByDefault = true, playbackSpeed = 1.5f) }
Card Options
CardOptions(
    aspectRatio = AspectRatio.PORTRAIT_9_16,   // LANDSCAPE_16_9 | SQUARE_1_1 | INSTAGRAM_4_5
    thumbnailQuality = ThumbnailQuality.HIGH,  // LOW | MEDIUM | HIGH | ORIGINAL
    cardElevationDp = 0f,
    cardCornerRadiusDp = 12f,
    cardBackgroundColor = 0xFF000000.toInt(),
)
Live update
FyreMemories.updateCardOptions { it.copy(cardCornerRadiusDp = 0f) }
Layout Options
LayoutOptions(
    carouselPaddingDp = 8,
    carouselMarginStartDp = 0, carouselMarginEndDp = 0,
    carouselMarginTopDp = 0,  carouselMarginBottomDp = 0,
    headerPaddingStartDp = 12, headerPaddingEndDp = 12,
    headerPaddingTopDp = 12,  headerPaddingBottomDp = 4,
    headerBackgroundColor = Color.TRANSPARENT,
    viewAllPaddingDp = 4, viewAllBackgroundColor = Color.TRANSPARENT,
    thumbnailCornerRadiusDp = 12f,
    showLoadingIndicator = true,
    loadingIndicatorColor = Color.WHITE,
    loadingIndicatorSizeDp = 24f,
    showPlayButton = true,
    playButtonColor = 0xFF6750A4.toInt(),
    playButtonBackgroundColor = Color.TRANSPARENT,
    playButtonSizeDp = 24f,
    feedButtonPaddingDp = 8, feedButtonMarginDp = 12,
)
Live update
FyreMemories.updateLayoutOptions { it.copy(thumbnailCornerRadiusDp = 0f) }
Typography Options

Controls the header title, header button ("View All"), and feed position text.

TypographyOptions(
    fontFamily = Typeface.createFromAsset(assets, "fonts/MyFont.ttf"),
    headerTitleTypeface = null,      // null = use fontFamily
    headerTitleSizeSp = 18f,
    headerTitleWeight = Typeface.BOLD,
    headerTitleColor = Color.WHITE,
    headerTitleLetterSpacing = 0f,
    headerButtonTypeface = null,
    headerButtonSizeSp = 14f,
    headerButtonWeight = Typeface.NORMAL,
    headerButtonColor = Color.WHITE,
    headerButtonAllCaps = false,
    positionTextSizeSp = 14f,
    positionTextWeight = Typeface.NORMAL,
)
Live update
FyreMemories.updateTypographyOptions { it.copy(headerTitleColor = Color.BLACK) }
Gradient Options
GradientOptions(
    showGradient = true,
    gradientOpacity = 0.4f,                              // 0.0 - 1.0
    gradientTopColor = 0x00000000,
    gradientBottomColor = 0xFF000000.toInt(),
    gradientDirection = GradientDirection.TOP_TO_BOTTOM, // TOP_TO_BOTTOM | BOTTOM_TO_TOP | LEFT_TO_RIGHT | DIAGONAL
)
Live update
FyreMemories.updateGradientOptions { it.copy(gradientOpacity = 0.6f) }
Animation Options

A master toggle plus per-effect controls for the like pop, loading shimmer, and button-press feedback.

AnimationOptions(
    enableAnimations = true,             // master toggle for all animations
    enableHeartAnimation = true,         // like pop
    enableShimmerAnimation = true,       // loading shimmer
    enableButtonPressAnimation = true,   // button press feedback
    heartAnimationDurationMs = 300L,
    shimmerAnimationDurationMs = 1000L,
    buttonPressAnimationDurationMs = 150L,
)
Live update
FyreMemories.updateAnimationOptions { it.copy(enableHeartAnimation = false) }
Progress Options

Controls the feed seekbar and the loading indicator.

ProgressOptions(
    seekBarThicknessDp = 4f,
    seekBarScrubberSizeDp = 12f,
    seekBarCornerRadiusDp = 2f,
    loadingIndicatorSizeDp = 24f,
    loadingIndicatorThicknessDp = 3f,
    loadingIndicatorColor = Color.WHITE,
    progressIndicatorStyle = ProgressIndicatorStyle.LINEAR, // LINEAR | CIRCULAR
)
Live update
FyreMemories.updateProgressOptions { it.copy(progressIndicatorStyle = ProgressIndicatorStyle.CIRCULAR) }
Haptic Options
HapticOptions(
    enableHaptics = true,
    hapticOnLike = true,
    hapticOnActionButtons = true,
    hapticOnNavigation = false,
    hapticOnLongPress = true,
)
Live update
FyreMemories.updateHapticOptions { it.copy(enableHaptics = false) }
Sound Options
SoundOptions(
    enableSounds = true,
    soundOnSwipe = false,
    soundOnLike = true,
    soundOnActionSuccess = true,
    likeSoundResId = R.raw.pop,      // @RawRes
    soundVolume = 0.7f,              // 0.0 - 1.0
)
Live update
FyreMemories.updateSoundOptions { it.copy(enableSounds = true, soundVolume = 0.5f) }
Icon Button Options

Per-button visibility, color, size, and custom Drawable icons for the feed Like, Save, Share, and Play buttons.

IconButtonOptions(
    showLikeButton = true,
    likeButtonColor = Color.WHITE, likedButtonColor = Color.RED,
    likeButtonSizeDp = 28f,
    showSaveButton = true, saveButtonColor = Color.WHITE,
    showShareButton = true, shareButtonColor = Color.WHITE,
    showPlayButton = true, playButtonSizeDp = 48f,
    buttonPaddingDp = 8, buttonMarginDp = 12,
)
Live update
val heart = ContextCompat.getDrawable(context, R.drawable.ic_heart)
FyreMemories.updateIconButtonOptions {
    it.copy(showSaveButton = false, customLikedIcon = heart, likedButtonColor = Color.RED)
}
Analytics Options
AnalyticsOptions(
    enabled = true,
    enabledEvents = emptySet(),  // empty = collect all; or setOf("PLAYED", "LIKED")
    flushIntervalMs = 30_000L,
    flushBatchSize = 20,
    debugOnly = false,           // log events but don't send them
)
Live updates
FyreMemories.setAnalyticsEnabled(false)  // consent screen helper
FyreMemories.updateAnalyticsOptions { it.copy(debugOnly = true) }
Carousel Customization

Set styling on the MemoriesCarouselView instance directly. Each instance has independent state, so you can show several carousels with different looks.

Multiple carousels with different styles
// Compact cards, subtle gradient
val featuredCarousel = MemoriesCarouselView(context).apply {
    bind(this@Activity, userId)
    withLayout(thumbnailCornerRadiusDp = 4f, carouselPaddingDp = 4)
    withGradient(gradientOpacity = 0.2f)
}

// Larger cards, custom title, stronger gradient
val recentCarousel = MemoriesCarouselView(context).apply {
    bind(this@Activity, userId)
    setTitle("Recent Memories")
    setShowViewAll(true)
    withLayout(thumbnailCornerRadiusDp = 16f, carouselPaddingDp = 12)
    withTypography(headerTitleSizeSp = 20f, headerTitleColor = Color.BLACK)
    withGradient(gradientOpacity = 0.7f, gradientDirection = GradientDirection.BOTTOM_TO_TOP)
    withCard(aspectRatio = AspectRatio.PORTRAIT_9_16, cardElevationDp = 4f)
}
Fluent chaining

The with* methods return the view, so you can chain them and end with bind().

carousel
    .withLayout(thumbnailCornerRadiusDp = 8f)
    .withGradient(showGradient = false)
    .withTypography(headerTitleColor = Color.WHITE)
    .withHaptics(enableHaptics = true, hapticOnLike = true)
    .bind(lifecycleOwner, userId)
Presentation mode
carousel.setPresentationMode(PresentationMode.CIRCLE)    // default: rings
carousel.setPresentationMode(PresentationMode.STORIES)  // card tiles
Header
carousel.setTitle("Recent Memories")
carousel.setShowViewAll(true)
carousel.setViewAllText("See All")
carousel.setTitleColor(Color.WHITE)
carousel.setHeaderBackground(Color.TRANSPARENT)
carousel.setTitleSize(18f)
Thumbnail, ring, dimensions
carousel.setThumbSize(80)
carousel.setThumbSpacing(8)
carousel.setRingColor(Color.WHITE)
carousel.setShowRing(true)
carousel.setRingWidth(2)
carousel.setCarouselHeight(120)
carousel.setCarouselMargins(start = 16, top = 8, end = 16, bottom = 8)
carousel.setHeaderPaddingStart(12)
carousel.configureCarousel(thumbSizeDp = 80, thumbSpacingDp = 8, ringColor = Color.WHITE, ringWidthDp = 2)
Event Listeners

Register listeners to observe events. Pass nullto remove one. All callbacks run on the main thread, so don't block them.

// Called when user taps a memory (before feed opens)
FyreMemories.setOnMemoryClickListener { memory -> }

// Called when a memory starts playing
FyreMemories.setOnMemoryViewListener { memory -> }

FyreMemories.setOnPlaybackCompletedListener { memory, position, durationMs -> }

// Called when feed is opened or closed
FyreMemories.setOnFeedLifecycleListener(object : OnFeedLifecycleListener {
    override fun onFeedOpened() { }
    override fun onFeedClosed() { }
})

// Called when user taps like / save / share / download
FyreMemories.setOnFeedActionClickListener { memory, action -> }

// SDK errors
FyreMemories.setOnFyreErrorListener { error, context -> }
Paywall Interceptor

An interceptor can prevent or defer the feed from opening — for example to gate it behind a paywall or ad. It receives the tapped memory and a proceed callback; call proceed() to let the feed open.

FyreMemories.setMemoryClickInterceptor { memory, proceed ->
    if (userHasPremium) {
        proceed()
    } else {
        showUpgradePrompt()
    }
}

To show an interstitial ad first, call proceed() when the ad is dismissed.

Share & Download Providers

Providers supply behavior the SDK needs. Return null to fall back to the SDK default.

Custom share intent
FyreMemories.setShareIntentBuilder { memory, contentUri ->
    Intent(Intent.ACTION_SEND).apply {
        type = "video/mp4"
        putExtra(Intent.EXTRA_STREAM, contentUri)
        putExtra(Intent.EXTRA_TEXT, "Check this out")
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
}
Custom download location
FyreMemories.setDownloadLocationProvider(object : DownloadLocationProvider {
    override fun directory(memory: MemoryItem): File? =
        File(filesDir, "fyre-downloads").apply { mkdirs() }

    override fun fileName(memory: MemoryItem): String? =
        "memory_" + memory.id + ".mp4"

    override fun onDownloadComplete(memory: MemoryItem, file: File) { }
    override fun onDownloadError(memory: MemoryItem, error: Throwable) { }
})
Custom Analytics Sink

Replace the default POST /analytics delivery with your own pipeline. Return true when the batch is delivered, or false to keep it for retry. It runs off the main thread, so blocking network I/O is fine here.

FyreMemories.setAnalyticsDispatcher { events: List<MemoryEvent> ->
    try {
        myAnalytics.upload(events)
        true                 // delivered
    } catch (e: Exception) {
        false                // keep for the next flush
    }
}

// Reset to the SDK default:
FyreMemories.setAnalyticsDispatcher(null)
Analytics Events

Standard interactions are logged for you automatically. Events are batched, persisted to Room, and flushed by size, timer, or when the app is backgrounded.

Event types
enum class EventType {
    VIEWED, PLAYED, PAUSED, COMPLETED, REPLAYED,
    LIKED, UNLIKED, SHARED, DOWNLOADED, SAVED_TO_CLOUD, RATED, ERRORED
}
Log a custom event

Use this only for partner-defined engagement dimensions. Most events are automatic.

FyreMemories.logEvent(
    videoId = memory.id,
    eventType = EventType.RATED,
    properties = mapOf("stars" to 5, "surface" to "home_carousel")
)
Data Models

Every memory implements MemoryItem, a sealed interface.VideoMemory is the only type in this release. Handle types with a when and an else branch so future types stay forward-compatible.

sealed interface MemoryItem {
    val id: String            // stable id for UI and analytics
    val sourceKey: String     // raw backend key (use with saveMemory)
    val thumbnailUrl: String? // CDN thumbnail URL, or null
    val createdAt: Instant?
    val isViewed: Boolean?
}

data class VideoMemory(
    override val id: String,
    override val sourceKey: String,
    override val thumbnailUrl: String?,
    override val createdAt: Instant?,
    override val isViewed: Boolean?,
    val videoUrl: String,        // CDN MP4 URL, ready for ExoPlayer
    val durationMs: Long? = null,
    val isLiked: Boolean? = null,
    val viewCount: Long? = null,
) : MemoryItem

when (memory) {
    is VideoMemory -> player.load(memory.videoUrl)
    else -> { /* future memory types */ }
}
Error Handling

Data calls return a FyreResult<T> instead of throwing. Every failure maps to a FyreError subtype, so raw exceptions never escape the public API.

ErrorMeaning
UnauthorizedToken missing or expired and refresh failed.
ForbiddenServer rejected the request (HTTP 403).
NotFoundResource not found (HTTP 404). Carries an optional resourceId.
TransientTimeout or 408/5xx. Safe to retry. Carries an optional httpCode.
NetworkI/O failure such as no connectivity or DNS.
ProtocolResponse could not be parsed.
UnknownAnything else.
fun FyreError.toUserMessage(): String = when (this) {
    is FyreError.Network      -> "You're offline. Check your connection."
    is FyreError.Transient    -> "Something went wrong. Pull to retry."
    is FyreError.Unauthorized -> "Please sign in again."
    is FyreError.Forbidden    -> "You don't have access to this content."
    is FyreError.NotFound     -> "That memory is no longer available."
    is FyreError.Protocol     -> "We received an unexpected response."
    is FyreError.Unknown      -> "An unexpected error occurred."
}
â„šī¸
List fetches never fail on emptyFor list fetches, a backend 404 (no memories yet) is returned as Success(emptyList()), not NotFound. You mainly see NotFound on specific-resource operations.
Timber Logging

Route SDK logs anywhere with a FyreLogger. It is only called when enableLogging is true.

FyreMemoriesConfig(
    enableLogging = BuildConfig.DEBUG,
    logger = FyreLogger { level, tag, message, throwable ->
        when (level) {
            FyreLogLevel.ERROR -> Timber.tag(tag).e(throwable, message)
            FyreLogLevel.WARN  -> Timber.tag(tag).w(message)
            else               -> Timber.tag(tag).d(message)
        }
    }
)
â„šī¸
Release builds force logging offPass enableLogging = BuildConfig.DEBUG so verbose logs are off in release builds. On non-debuggable release APKs, the SDK also calls FyreLog.applyReleaseGuard during init() and forces logging off even if enableLogging was true.
ProGuard / R8

No rules needed. The SDK ships consumer rules inside the AAR. They are applied automatically when building with R8 or ProGuard. After enabling minification, do a release smoke test of feed playback, the action buttons, analytics delivery, and JSON parsing.

Going to Production

Verify these before shipping a release build.

  • Firebase Auth is configured and a user is always signed in before init().
  • enableLogging = BuildConfig.DEBUG so verbose logs stay off in release.
  • Release logger routes SDK logs to your crash reporter.
  • Analytics consent is wired with setAnalyticsEnabled() or updateAnalyticsOptions if your jurisdiction requires opt-in.
  • clearUser() is called on sign-out, and re-init() on the next sign-in is verified.
  • Account switching is tested with setUser() so there is no cross-user data leakage.
  • baseUrl points at the right environment, or is empty for production.
  • Cache sizes are tuned to your storage budget and set before init().
  • No FileProvider authority clash in the merged manifest.
  • R8 or ProGuard release build is smoke-tested: playback, actions, analytics, JSON parsing.
  • WRITE_EXTERNAL_STORAGE runtime request is handled on API 28 and below if Download is enabled.
  • Empty state is handled: the carousel auto-hides when there are no memories.
  • Network and transient errors are surfaced with retry affordances.
  • A global OnFyreErrorListener is registered.
Requirements
â„šī¸
User-Agent reports a different versionThe SDK's HTTP User-Agent reports version 1.0.0 (FyreHttpClient.SDK_VERSION), independent of the published Maven artifact version (1.0.1). This is expected — not a version mismatch bug.
RequirementDetails
Min SDK24 (Android 7.0+)
Compile SDK35
Java / KotlinToolchain target 17
RepositoryMaven Central (com.fyrememories:fyre-memories-sdk:1.0.1)
Firebase AuthUser must be signed in before FyreMemories.init()
Internet permissionAdd to AndroidManifest.xml if not already present.
Was this helpful?