FyreMemories Android SDK documentation
Overview
This is the client SDK for the FyreMemories platform. It adds a photo and video memories experience to your Android app, backed by the FyreMemories cloud processing engine. 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.
One line in build.gradle.kts
implementation("com.fyrememories:fyre-memories-sdk:1.0.1").
Call FyreMemories.init() once a Firebase user is signed in.
Drop MemoriesCarouselView into any layout, or call FyreMemories.openFeed().
Everything funnels through one entry point: FyreMemories. Everything beyond step 3 is optional.
| Surface | Class | Use it for |
|---|---|---|
| Carousel | MemoriesCarouselView | A horizontal thumbnail strip you embed in your own screens. |
| Full-screen feed | MemoriesFeedActivity | A swipeable, auto-playing video player with like, save, share, and download. |
| Headless | MemoriesRepository | Fetch 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.
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}dependencies {
implementation("com.fyrememories:fyre-memories-sdk:1.0.1")
}Module configuration
The SDK targets Java 17. Your module must compile against a matching toolchain.
android {
compileSdk = 35
defaultConfig { minSdk = 24 }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions { jvmTarget = "17" }
}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.
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, or any other 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)
}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,
)
)| Field | Default | Notes |
|---|---|---|
baseUrl | "" | Backend base URL. Empty uses the built-in production URL. Include the scheme, no trailing slash. |
customization | null | Full UI customization tree. null uses defaults. |
enableLogging | false | Verbose SDK logs. Pass BuildConfig.DEBUG so logs stay off in release. |
cacheSizeBytes | 200 MB | ExoPlayer 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. |
thumbnailCacheSizeBytes | 50 MB | Coil thumbnail disk cache size. |
themeOverlay | 0 | Theme overlay applied to all SDK views and the feed activity. 0 means none. |
logger | null | Forward SDK logs to Timber or Crashlytics. Only called when enableLogging is true. |
connectTimeoutMs | 10 000 | OkHttp connect timeout. |
readTimeoutMs | 15 000 | OkHttp read timeout. |
writeTimeoutMs | 15 000 | OkHttp 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 outSwitch 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()Carousel View
MemoriesCarouselView is a LinearLayout subclass. It works fully programmatically, and XML is also supported.bind() is required: the view stays hidden until you bind it, and it auto-hides if the user has no memories.
<io.fyre.memories.ui.MemoriesCarouselView
android:id="@+id/memoriesCarousel"
android:layout_width="match_parent"
android:layout_height="wrap_content" />val userId = FyreMemories.currentUserId() ?: return
val carousel = findViewById<MemoriesCarouselView>(R.id.memoriesCarousel)
carousel.bind(lifecycleOwner = this, userId = userId)Optional: handle taps yourself
The local listener consumes the tap. Without it, the carousel opens the feed at the tapped position by default, unless a MemoryClickInterceptor is set (see Paywall Interceptor), which can also gate the default open.
carousel.onMemoryClickListener = OnMemoryClickListener { memory, position ->
MemoriesFeedActivity.start(this, userId, startPosition = position)
}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 memoryDirect 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) }
}
}| Member | Description |
|---|---|
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. |
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 tilesHeader
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.
| Error | Meaning |
|---|---|
Unauthorized | Token missing or expired and refresh failed. |
Forbidden | Server rejected the request (HTTP 403). |
NotFound | Resource not found (HTTP 404). Carries an optional resourceId. |
Transient | Timeout or 408/5xx. Safe to retry. Carries an optional httpCode. |
Network | I/O failure such as no connectivity or DNS. |
Protocol | Response could not be parsed. |
Unknown | Anything 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."
}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)
}
}
)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
| Requirement | Details |
|---|---|
| Min SDK | 24 (Android 6.0+) |
| Compile SDK | 35 |
| Java / Kotlin | Toolchain target 17 |
| Repository | Maven Central (com.fyrememories:fyre-memories-sdk:1.0.1) |
| Firebase Auth | User must be signed in before FyreMemories.init() |
| Internet permission | Add to AndroidManifest.xml if not already present. |