![]()
I Built a Tiny Swipe-Based Gallery Cleaner in Kotlin + Compose Only a Few MB
In the modern digital era, our smartphones have become the primary repository for our most cherished memories. High-resolution cameras and infinite cloud storage possibilities have led to an exponential growth in the number of photos we capture. However, this convenience comes with a significant downside: digital hoarding. We accumulate thousands of screenshots, blurry images, duplicate shots, and obsolete memes, resulting in a cluttered, disorganized photo gallery that slows down device performance and makes finding specific moments a frustrating endeavor.
Addressing this ubiquitous problem, we have developed a minimalist yet powerful Android application: a swipe-based gallery cleaner. This app is not just another utility; it is a carefully engineered solution designed to transform the tedious task of photo management into a fluid, intuitive experience. By leveraging the modern Kotlin programming language and the declarative power of Jetpack Compose, we have created an application that is not only lightweight—weighing in at just a few megabytes—but also exceptionally performant and user-friendly.
Our mission was to strip away the bloatware features found in many gallery management apps and focus entirely on the core functionality: rapidly deciding the fate of your images. Inspired by the frictionless swiping mechanics of popular dating applications, we have gamified the decluttering process. This article details the architecture, design philosophy, and technical implementation of our lightweight gallery cleaner, offering a comprehensive look at how we achieved a seamless user experience with a minimal footprint.
The Problem of Digital Photo Clutter
Before delving into the technical specifics of our application, it is crucial to understand the scope of the problem we aimed to solve. The average Android user possesses thousands of photos. Over time, a significant portion of these images lose their value. We take multiple shots to get the perfect angle, save screenshots for temporary reference, or download images that are viewed once and forgotten.
The Performance Impact of Unoptimized Media
A gallery filled with unnecessary data does more than just cause visual noise. It impacts the device’s performance. Media scanning services must index every file, consuming system resources. Backup applications attempt to upload gigabytes of low-value data, draining battery life and consuming bandwidth. Furthermore, large local storage usage can lead to sluggish file system operations, affecting the overall responsiveness of the device.
The Psychological Burden of Clutter
Beyond technical performance, there is a psychological component to digital clutter. A disorganized photo roll creates a subtle sense of anxiety. Searching for a specific memory amidst a sea of irrelevance is time-consuming and frustrating. Users often express a desire to “clean up” their galleries but abandon the task because traditional gallery apps require tedious tapping, selecting, and deleting—actions that are repetitive and fatiguing for the thumb.
Existing Solutions: Over-Engineered or Inefficient
The market is saturated with “cleaner” apps, but many suffer from significant drawbacks. Some are laden with intrusive advertisements, while others require excessive permissions, raising privacy concerns. Many existing solutions are heavy, requiring hundreds of megabytes of storage space just to perform a simple function. We recognized a gap in the market for a tool that is transparent, fast, privacy-focused, and aesthetically pleasing. Our solution addresses these pain points by focusing on a single, refined interaction model: the swipe.
Technical Architecture: Kotlin and Jetpack Compose
The decision to build this application using Kotlin and Jetpack Compose was deliberate and strategic. These technologies represent the modern standard for Android development, offering distinct advantages over legacy frameworks like Java and XML-based layouts.
The Power of Kotlin
Kotlin is a statically typed programming language developed by JetBrains and officially supported by Google for Android development. Its concise syntax reduces boilerplate code significantly, allowing developers to express logic with fewer lines while maintaining readability and type safety. For a lightweight app, every kilobyte counts. Kotlin’s features, such as lambda expressions, data classes, and extension functions, enabled us to streamline the codebase. This results in a smaller APK size, which is a critical factor for users conscious of their storage usage.
Jetpack Compose: A Declarative UI Paradigm
Jetpack Compose is Android’s modern toolkit for building native UI. Unlike the traditional View system, which is imperative, Compose is declarative. This means the UI is a direct reflection of the app’s state. When the state changes (for example, a photo is deleted), the UI automatically updates to reflect that change. This paradigm shift simplifies UI development and drastically reduces the code required to manage complex界面 interactions.
For our swipe-based gallery, Compose offers specific advantages. Its gesture detection APIs are robust and easy to implement. We can define how a card reacts to drag events with just a few lines of code. Furthermore, Compose’s lazy loading capabilities (specifically LazyColumn and LazyVerticalGrid) are essential for rendering large collections of images efficiently. These components only compose the items currently visible on the screen, ensuring that the app remains responsive even when scrolling through thousands of photos.
Achieving a Lightweight Footprint (Under 2MB)
One of our proudest achievements is keeping the application size to just a few megabytes. In an era where apps routinely exceed 100MB, this minimal footprint is a testament to efficient engineering. We avoided heavy third-party libraries for image loading and networking, opting instead for Android’s native Coil library, which is built on Kotlin Coroutines and excels at loading images quickly with minimal memory overhead.
By stripping away unnecessary dependencies and utilizing the compact nature of Kotlin/Compose, we ensured that the app downloads quickly even on slower networks and occupies negligible storage space. This lightweight nature also contributes to faster startup times and reduced RAM usage, preventing the app from bogging down the user’s device.
Core Features and Functionality
Our gallery cleaner is built around a singular, focused workflow. We stripped away non-essential features to create a streamlined user experience centered on the “Keep vs. Delete” decision-making process.
The Tinder-Inspired Swipe Mechanic
The defining feature of our app is the swipe-based interface. Drawing inspiration from the ubiquitous swiping gestures of dating apps, we implemented a card-stack layout. Each photo is presented as a card that the user can interact with.
- Swipe Right (Keep): Swiping a photo to the right indicates satisfaction with the image. The card slides off the screen to the right, and the photo remains in the gallery.
- Swipe Left (Delete): Swiping a photo to the left removes it from view, signaling the user’s intent to delete the file.
- Vertical Swipe (Ignore): Some users prefer to simply skip a photo without making a decision. We implemented a vertical swipe gesture to push the card to the back of the queue, allowing for non-linear browsing.
This interaction model is ergonomic and fast. It utilizes the natural reach of the thumb and minimizes the cognitive load required to make a decision. The fluid animations provided by Jetpack Compose ensure that these transitions are smooth and satisfying, encouraging users to continue cleaning their gallery.
Local-First Privacy Architecture
Privacy is a paramount concern in the current app ecosystem. Many cleaner apps upload thumbnails or metadata to the cloud for processing. We strictly oppose this practice. Our application operates entirely on-device.
- No Internet Permission: The app does not request internet access. It cannot send your photos to external servers.
- Local File System Access: The app interacts directly with the Android MediaStore API. Files are deleted from the device’s storage only when the user explicitly swipes left.
- Zero Data Collection: We do not collect analytics, user behavior data, or any personally identifiable information. The app functions offline, ensuring that your memories remain yours alone.
Native Localization Support
Recognizing our global user base, we built the application with robust localization support from the ground up. Using Android’s resource system and Kotlin’s string handling, the interface automatically adapts to the user’s system language. We currently support a wide array of languages, including English, Spanish, French, German, Chinese, and more. This ensures that users worldwide can navigate the app intuitively without language barriers.
Efficient Image Rendering
Performance is critical when dealing with large media libraries. We utilize Coil for image loading, which efficiently manages memory by using an in-memory cache and downsampling images to fit the UI view. This prevents OutOfMemory errors, which are common in poorly optimized gallery apps. Furthermore, we implemented placeholders and cross-fade animations to ensure a polished visual experience while images load from the storage.
Deep Dive into the Development Process
Building a seemingly simple app like a gallery cleaner requires rigorous planning and precise execution. Here is an inside look at the development journey.
State Management in Compose
Managing state is the heart of any Compose application. For our gallery, we defined a ViewModel that holds the list of images and the current swipe state.
- Flow of Data: We query the MediaStore to fetch image URIs. This data is converted into a
StateFlowin the ViewModel. - UI State: The Compose UI collects this flow using
collectAsState. When the user swipes a card, we update the state in the ViewModel. - Undo Functionality: To prevent accidental deletions, we implemented a temporary “trash” state. When a photo is deleted, it is moved to a temporary holding area for a few seconds, displaying an “Undo” snackbar. If the user taps Undo, the file is restored; otherwise, the file is permanently deleted from the storage. This requires careful handling of file descriptors and ensuring that the file is not immediately unrecoverable.
Handling Permissions Gracefully
Android’s storage permissions have evolved with Scoped Storage. Our app targets the latest Android versions and handles permissions responsibly.
- Read Access: The app requires
READ_EXTERNAL_STORAGEto display photos. - Write Access: To delete photos,
WRITE_EXTERNAL_STORAGE(or using the MediaStorecreateWriteRequestfor higher API levels) is necessary. We implemented a clear permission flow where the user is prompted only when they attempt to load their gallery. If permission is denied, the app displays a friendly message explaining why it is needed, rather than crashing or showing a blank screen.
Animation and Haptics
The “feel” of an app is just as important as its functionality. We leveraged Compose’s AnimatedVisibility and animate*AsState APIs to create buttery-smooth transitions. When a card is swiped away, it doesn’t just disappear; it slides, rotates, and fades out, providing visual feedback that reinforces the user’s action.
Additionally, we integrated haptic feedback. A subtle vibration occurs when the user initiates a swipe, and a distinct pattern plays when a photo is successfully deleted. This tactile feedback makes the digital interaction feel physical and grounded.
UI/UX Design Principles
Our design philosophy revolves around minimalism and clarity. The interface is designed to be invisible, allowing the photos to take center stage.
Visual Hierarchy and Color Palette
We utilized a neutral color palette with high contrast to distinguish actions. Typically, green or blue tones indicate “Keep,” while red tones indicate “Delete.” However, to avoid color blindness issues, we also use distinct icons and text labels. The app layout consists of a top bar with a simple title and a settings icon, and a central content area containing the card stack. The bottom bar provides context, such as the remaining image count and progress indicators. We avoided heavy shadows and complex gradients, sticking to Material Design 3 guidelines for a clean, modern look.
Reducing Cognitive Load
A major challenge in photo cleaning is “decision fatigue.” To mitigate this, we designed the UI to present one task at a time: Keep or Delete? We removed progress bars that constantly remind the user of the remaining work, focusing instead on the immediate task. The swipe mechanics allow the user to enter a “flow state,” where decisions become rapid and intuitive.
Accessibility Considerations
While the app is swipe-centric, we ensured it remains accessible.
- Screen Readers: We applied proper semantics to the UI elements, allowing TalkBack users to understand what action a swipe will perform.
- High Contrast: Text and icons are designed to meet WCAG contrast standards.
- Touch Targets: All interactive elements, including the “Undo” button, are sized appropriately to accommodate various finger sizes.
Performance Optimization Strategies
A “tiny” app must also be a “fast” app. We implemented several optimization strategies to ensure the app runs smoothly on low-end devices as well as flagship phones.
Memory Management
Handling thousands of image URIs requires careful memory management. We do not load full-resolution bitmaps into memory. Instead, we load thumbnails provided by the MediaStore. This significantly reduces RAM usage. The Coil library handles the caching strategy, ensuring that once an image is viewed, it stays in the memory cache for rapid re-access without hitting the disk again.
Asynchronous Processing
File I/O operations (reading the gallery and deleting files) are performed on background threads using Kotlin Coroutines. The Dispatchers.IO context is used for these heavy operations, ensuring that the UI thread remains unblocked. This guarantees that animations and touch responses remain 60fps (frames per second), even while the app is scanning the device for images.
Lazy Loading with Compose
As mentioned, Jetpack Compose’s lazy layouts are a game-changer. When the user browses the gallery in grid view (if we implement a secondary view) or simply scrolls through the list of pending decisions, Compose recycles the UI components. It does not render items that are off-screen. This technique allows the app to handle libraries containing tens of thousands of images without stuttering or lagging.
Challenges and Solutions in Kotlin Compose
Developing with a relatively new framework like Jetpack Compose presents unique challenges. We navigated these to deliver a stable product.
Gesture Handling Complexity
Coordinating swipe gestures with the underlying scrollable containers can be tricky. If the sensitivity is too high, accidental swipes occur; too low, and the app feels unresponsive. We fine-tuned the pointerInput modifiers to detect horizontal swipes distinctly from vertical scrolls. We also implemented a threshold logic: the card must be dragged a certain percentage of the screen width before the delete action is triggered.
List Performance with Large Datasets
Initially, rendering a list of 10,000 images caused recomposition bottlenecks. We optimized this by ensuring that we only pass stable references to Composables. We used remember effectively to prevent unnecessary recalculations of image bitmaps and utilized derivedStateOf to compute the progress bar updates only when the underlying dataset changed, rather than on every frame.
Localization and Global Reach
To ensure our app resonates with a global audience, we treated localization as a core feature, not an afterthought.
String Resource Management
We structured our string resources strictly, avoiding hard-coded text. Every user-facing string is stored in strings.xml with appropriate placeholders and formatting arguments. This structure makes it seamless for community contributors to add new languages or for professional translators to provide accurate localization.
Cultural Adaptation
Beyond direct translation, we considered cultural contexts. For example, the interpretation of colors (e.g., red for “delete” vs. “good luck”) varies. Our design uses universally understood icons alongside text to bridge potential cultural gaps. The swipe direction (left vs. right) is also intuitive across most cultures, as it mimics physical interactions like flipping through a deck of cards.
Future Roadmap and Potential Features
While the current version is lean and focused, we have a roadmap for future enhancements that align with our philosophy of minimalism.
Smart Filtering and AI Suggestions
Future versions may leverage on-device Machine Learning (ML) to categorize images. We could implement a “fuzzy” detector to identify blurry photos or an OCR scanner to find text-heavy screenshots that might no longer be relevant. These features would be computed entirely locally to maintain our privacy-first promise.
Advanced Sorting and Tagging
We plan to introduce basic sorting options, allowing users to filter their gallery by date, size, or resolution before starting the cleaning process. A “favorites” tagging system could also allow users to quickly archive important photos without leaving the app interface.
Cloud Sync Integration (Optional)
While the core app is offline, we are considering a modular approach where users can optionally enable a plugin to sync their “kept” photos to specific cloud providers. This would remain an opt-in feature, ensuring the base app remains lightweight and offline-first.
Conclusion: A New Standard for Gallery Management
We have successfully engineered a swipe-based gallery cleaner that meets the demands of modern Android users. By utilizing Kotlin and Jetpack Compose, we delivered a high-performance application with a footprint of only a few megabytes. The Tinder-inspired swipe mechanic transforms a mundane chore into an engaging, fluid experience.
Our commitment to privacy, localization, and lightweight architecture sets this application apart from the bloated alternatives currently available. We believe that software should serve the user, respect their device resources, and protect their data. This app is the embodiment of those principles. Whether you are a power user seeking to optimize your device’s performance or a casual user looking to rediscover the joy of a decluttered photo library, our solution provides the perfect tool for the job.
We invite you to experience the future of photo management. Swipe away the clutter, keep the memories, and enjoy a streamlined, responsive gallery experience tailored to your needs. Download the app today and take the first step toward digital organization.
Magisk Modules: Enhancing Your Android Experience
Welcome to Magisk Modules, your premier destination for Android customization and system enhancement. At Magisk Module Repository (https://magiskmodule.gitlab.io), we host a comprehensive collection of modules designed to elevate your device’s performance, aesthetics, and functionality.
Our repository is dedicated to providing high-quality, safe, and innovative modules that leverage the power of Magisk root. Whether you are looking to optimize system resources, install custom fonts, or add unique features unavailable in standard firmware, our repository is the ultimate resource for the Android enthusiast community. We ensure that every module is tested and curated to maintain system stability while pushing the boundaries of what your Android device can achieve. Visit us today to explore the full potential of your device.