Search Docs
Loading...
Skip to content

Memories in Android

Turn a set of photos and video clips into a shareable memory montage on Android. The kit picks up media from the gallery, arranges it on a timeline with transitions and title cards, applies styled looks, layers in audio, and exports an MP4—entirely on the device with no server dependencies.

Memories starter kit screenshot

10 mins
estimated time
Download
GitHub

Pre-Requisites#

This guide assumes basic familiarity with Android and Kotlin. You will need:

  • Latest Android Studio
  • Kotlin: 1.9.10 or later
  • Gradle: 8.4 or later
  • Android: 7.0+ (API level 24+)

Get Started#

Start with a complete, runnable Android starter kit project.

Step 1: Clone the Repository#

Terminal window
git clone -b v1.84.0-rc.1 https://github.com/imgly/starterkit-memories-editor-android.git
cd starterkit-memories-editor-android

Step 2: Open and Run#

Create and launch a new android emulator or use an existing one, or connect a physical device with USB Debugging on.

Open the project in Android Studio, sync gradle via File -> Sync Project With Gradle Files and run the app module from the UI, or use:

Terminal window
./gradlew app:installDebug

The sample app launches MainActivity, which displays the full Memories flow: a photo picker that hands the selected media to the slideshow editor.

The full implementation of the starter kit lives in the starter-kit/ folder:

starter-kit/src/main/
├── assets/
│ └── ly.img.memories.style/ # The kit's own local asset source (backdrops + picker thumbnails)
│ ├── content.json # Describes the style assets; URIs use the {{base_url}} placeholder
│ ├── thumbnails/ # noir.png, hologram.png, bubblegum.png
│ └── animations/ # hologram.json, bubblegum.json (looping Lottie backdrops)
└── kotlin/ly/img/editor/configuration/memories/
├── MemoriesConfiguration.kt # Wires the editor's onCreate / dock / bottomPanel / navigationBar / overlay / onExport slots
├── MemoriesApp.kt # The full flow: photo picker → slideshow editor
├── MemoriesEditor.kt # Hosts the editor with the loading overlay and back handling
├── MemoriesViewModel.kt # Editor + montage state
├── callback/
│ ├── OnCreate.kt # Builds the scene, registers asset sources, wires event subscriptions
│ └── OnExport.kt # Export flow and handling (MP4)
├── component/ # Editor UI slots: Dock, BottomPanel, NavigationBar, Overlay, ExportOverlay
├── scene/ # Timeline assembly (the tracks are the source of truth)
│ ├── SceneSetup.kt # Builds the video scene and its tracks in code
│ ├── Timeline.kt # Lays every clip on one media track, in slot order
│ ├── Transitions.kt # TransitionSpec + wiring a style's blend onto every boundary
│ ├── TrackEditor.kt # Reads/writes the tracks and applies in-place edits
│ ├── Title.kt # Title card + burst-image intro
│ └── Playback.kt # Loop / volume helpers
├── style/ # The styled looks + their custom asset source
│ ├── VideoStyle.kt # Style catalog (filter, backdrop, transition, typeface) referencing assets by id
│ ├── StyleAssetSource.kt # Registers ly.img.memories.style via addLocalSourceFromJSON
│ └── StyleApplier.kt # Applies a style to the slideshow
├── screen/ # ImageSelectionScreen (picker) + LoadingScreen
├── sheet/ # Styles and Volume bottom sheets
├── widget/ # Reusable composables (grid, timeline strip, video thumbnail, …)
├── iconPack/ # Compose vector icons
├── model/ # Small data models (ImageItem, TimelineImage, ExportStatus)
└── util/
├── Animations.kt # The slide animations (edit this to change the motion)
└── Constants.kt # Timing, page size, title knobs

Set Up a Scene#

The Memories scene is built in code (no serialized scene file). The setup logic lives in scene/SceneSetup.kt and runs from the editor’s onCreate (callback/OnCreate.kt): it creates a video scene, lays out the persistent background and text tracks, then scene/Timeline.kt appends every photo and clip to a single media track in slot order.

The clips are siblings on that one track because a transition belongs to the outgoing clip and blends it into its neighbour—which only works between siblings. The track’s own time offset holds the title gap; everything inside it is positioned by the engine.

Transitions and Slide Motion#

Two things move in a Memories montage, and each has one job:

What moves Who owns it
Between two slides The blend from one clip to the next The style’s transition, applied by the engine
Within one slide A slow Ken Burns across the clip’s full duration util/Animations.kt

Keeping them apart is what lets both stay simple. Assigning a transition overlaps the pair and pulls every later clip earlier, so the engine—not the kit—owns the timeline: there is no start-time arithmetic anywhere in scene/. The engine also clamps each overlap to half of the shorter neighbour, so a clip shorter than the requested duration needs no special case. A 4s video between 8s photos simply gets 2s blends on either side.

Because the transition does the leaving, a slide needs no out-animation. AnimationPair.createOut is nullable and defaults to null, and the shipped recipe is a single Ken Burns spanning the whole clip with Linear easing—an eased curve spends its speed early or late, which over eight seconds reads as the photo lurching and then crawling.

starter-kit/src/main/kotlin/ly/img/editor/configuration/memories/util/Animations.kt
private fun kenBurnsDrift(): AnimationPair = AnimationPair(
createIn = { engine ->
engine.block.createAnimation(AnimationType.KenBurns).also { animation ->
// The whole clip, so the move is slow enough to read as life rather than motion.
engine.block.setDuration(animation, IMAGE_DURATION)
engine.block.setFloat(animation, "animation/ken_burns/zoomIntensity", 0.2f)
// A full crop-length of travel reads as the photo sliding past; a third of it reads
// as drift.
engine.block.setFloat(animation, "animation/ken_burns/travelDistanceRatio", 0.3f)
engine.block.setEnum(animation, "animationEasing", "Linear")
}
},
)

A Transition Per Style#

A transition is as much a style’s character as its filter is, so each one blends differently:

Style Transition Overlap
Default CrossFade 3.2s
Noir FadeToBlack 1.8s
Hologram CrossWarp 1.2s
Bubblegum ColorWipe, pink, sweeping up 1.0s

A style declares one as a TransitionSpec: the type, how long the clips overlap, and a configure lambda for the type’s own properties, which live under transition/{type}/{property} keypaths. Call findAllProperties on a created transition to see what a type exposes.

starter-kit/src/main/kotlin/ly/img/editor/configuration/memories/style/VideoStyle.kt
transition = TransitionSpec(
type = TransitionType.ColorWipe,
duration = 1.0,
configure = { engine, transition ->
engine.block.setEnum(transition, "transition/color-wipe/direction", "Up")
engine.block.setColor(transition, "transition/color-wipe/color", Color.fromHex("#FF4FA3"))
},
),

Because styles overlap by different amounts, choosing one changes how long the montage runs. style/StyleApplier.kt re-wires every boundary, reads the new end back off the engine, and re-stretches the backdrop to match—so nothing predicts a duration it can measure.

Styles From a Custom Asset Source#

The styled looks (Noir, Hologram, Bubblegum) get their backdrops and picker thumbnails from the kit’s own local asset source, ly.img.memories.style, rather than hard-coded paths. The assets stay bundled under starter-kit/src/main/assets/ly.img.memories.style/ and are described by content.json, which style/StyleAssetSource.kt registers with engine.asset.addLocalSourceFromJSON(...)—exactly like the default IMG.LY sources.

style/VideoStyle.kt then references each backdrop and thumbnail by asset id, and style/StyleApplier.kt resolves the actual URI from the source at apply time. Because the asset URIs in content.json use the portable {{base_url}} placeholder (which the engine substitutes with the basePath the source is registered against) instead of an absolute file:///android_asset/... path, the same source definition also works if you reuse it on iOS or Web.

starter-kit/src/main/assets/ly.img.memories.style/content.json
{
"uri": "{{base_url}}/ly.img.memories.style/animations/hologram.json",
"thumbUri": "{{base_url}}/ly.img.memories.style/thumbnails/hologram.png"
}

The two animated backdrops are Lottie, not video. A video fill plays either—the engine picks its decoder from the asset’s mimeType, so application/json routes the file through its Lottie renderer and video/mp4 through the video decoder. StyleBackground.Video is named for the fill, not the file. Vector costs a fraction of the bytes for this kind of abstract motion: the two backdrops here are 25 KB together, against 25 MB as MP4.

To add a look, drop its thumbnail (and backdrop, if any) into the assets folder, add an entry to content.json, and add the matching VideoStyle in style/VideoStyle.kt.

Where to Change Things#

The starter kit ships a generic structure and behavior, but every part of it is in your codebase and meant to be customized. The most common edit points:

  • Slide motion — util/Animations.kt. Add, remove, or tune the animation recipes a slide can use. Keep them alpha-free, for the reason above.
  • Transitions — the transition field on each VideoStyle, wired by scene/Transitions.kt.
  • Looks / filters (styles) — style/VideoStyle.kt. Each style bundles a filter, backdrop, transition, and title typeface, referencing the bundled assets by id (see above).
  • Timing, page size, title — util/Constants.kt (clip duration, the default transition overlap, canvas size, title duration).
  • Timeline assembly — scene/SceneSetup.kt, scene/Timeline.kt, and scene/Title.kt.

Customize Export Functionality#

Export handling lives in callback/OnExport.kt. The montage is rendered to an MP4 ByteBuffer; from there you can upload it to your server, save it to the device gallery, or share it. Closing the editor is driven by the onCloseEditor callback passed into MemoriesEditor (wired to the navigation-bar close button via rememberCloseEditor in component/NavigationBar.kt).


Troubleshooting#

Editor doesn’t load#

  • Check onCreate: Ensure the onCreate callback finishes building the scene and no coroutine is stuck.
  • Verify the baseURL: Assets must be reachable from the CDN or your self-hosted location.
  • Check logcat errors: Look for errors in Android logcat.

Export fails or produces blank output#

  • Wait for content to load: Ensure media is fully loaded before exporting.
  • Check logcat errors: Look for errors in Android logcat.

Next Steps#