User presses 'down' on the remote — focus jumps to the end of the list, skipping the 'Favorites' button. Sound familiar? That's a classic Focus Engine bug in Android TV apps. We design multimedia applications where navigation is predictable, the player adapts to the screen, and content shows up in Google TV recommendations. We handle development turnkey — from prototype to Google Play publication. Over 5 years, we have delivered 12 OTT projects; our accumulated experience ensures quality and compliance with Google requirements. 95% of our apps pass Google Play review on first submission, and users spend an average of 5 minutes per session on our TV apps.
Android TV vs Google TV
Google TV is an overlay on Android TV with a different launcher and content requirements. An app for Android TV may display incorrectly on Google TV due to differences in recommendation handling and deep links. We account for this at the design stage to avoid problems after release.
Configuring D-pad navigation in complex layouts
The main difference from mobile development is that the user controls via the D-pad remote. Focus moves via the Android Focus Engine, but nested horizontal RecyclerViews inside a vertical one (Netflix pattern) break the logic: pressing 'down' may jump focus to the end of the screen. The solution is a custom LinearLayoutManager with overridden onInterceptFocusSearch(). Without it, users may never reach the desired row.
The Leanback library (androidx.leanback) provides BrowseSupportFragment and RowsSupportFragment with native D-pad support, but it is considered deprecated. Jetpack Compose for TV (androidx.tv:tv-compose) offers TvLazyRow and TvLazyColumn with declarative focus management. For new projects, we choose Compose TV; for legacy, Leanback. Compose TV reduces code volume by 40% versus Leanback, leading to faster iterations.
| Characteristic |
Leanback |
Compose TV |
| Architecture |
Fragments, View |
Declarative Compose |
| Navigation |
Automatic, limited |
Custom via FocusRequester |
| Performance |
Higher on old devices |
Better on new (API 24+) |
| Development speed |
Lower due to templates |
Higher, less code |
| Google TV support |
Limited |
Full |
Integrating ExoPlayer with adaptive quality
androidx.media3:media3-exoplayer is the standard for Android TV. It is critical to properly configure TrackSelector: selecting a video track should take into account the display resolution, not just pick the highest — 4K on a 1080p screen wastes bandwidth. DRM Widevine L1 is available on certified devices (SHIELD TV, Chromecast with Google TV). We check the level via MediaDrm.isCryptoSchemeSupported(WIDEVINE_UUID) and securityLevel query. Media3 documentation recommends this approach. ExoPlayer outperforms deprecated MediaPlayer by 2x on low-end chipsets.
Integration with Google TV recommendations
Google TV displays personalized recommendations from apps via the WatchNext recommendations API. It requires a ContentProvider to fill in watch progress, update status, and remove completed programs. Fire TV uses a different mechanism — App-to-App Communication via Intent for deep linking to content.
How development proceeds: step-by-step
- Analysis of content, monetization, and target platform requirements.
- Design of navigation and focus management.
- Integration of ExoPlayer with custom track settings and DRM.
- Implementation of Google TV recommendations (WatchNext Programs).
- Testing on 5+ devices (NVIDIA Shield, Chromecast with Google TV, Xiaomi Mi Box, TCL TV, Sony Bravia).
- Preparation of launcher graphics and icons.
- Build and publication on Google Play following the Android TV developer guide.
- Technical support after release (1 month).
Certification and publication on Google Play
The app is published with <uses-feature android:name="android.software.leanback"/> and <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>. The latter is mandatory — without it, the Play Store will not show the app on TV. Google checks: navigation accessible via D-pad, no touch dependencies, correct handling of KEYCODE_BACK and KEYCODE_DPAD_*.
What's included in the work
- Source code with comments and build documentation.
- CI/CD configuration (GitHub Actions, Fastlane) for automatic publication to TestFlight and Google Play.
- Access to developer consoles (App Store Connect, Google Play Console).
- Test report on 5 devices with different OS versions.
- Training of the client's team on content admin panel usage.
- Post-release support for 1 month (critical bug fixes).
Typical development mistakes and how to avoid them
- Ignoring Focus Engine — user cannot reach buttons.
- Missing KEYCODE_BACK handling — app does not close.
- Incorrect TrackSelector configuration — buffering 4K on 1080p screen.
- Missing metadata for Google TV — no recommendations.
- Forgetting android.hardware.touchscreen:false — app not visible.
Timelines and cost
| App type |
Timeline |
| Informational on Leanback |
from 5 to 9 weeks |
| VOD with DRM, recommendations, offline |
from 12 to 20 weeks |
Costs vary by complexity: simple informational apps start from $15,000; full VOD platforms with DRM and recommendations range from $50,000 to $150,000. We offer a free project evaluation. Contact us to discuss details. Get a consultation for your project.
Development of Widgets, App Clips, and Live Activities: Entry Points Outside the App
We understand that users see your app not only when they open it. A widget on the home screen, a live score in Dynamic Island, a mini experience without installation — these are separate entry points that we implement within platform constraints. Over 5 years, we have developed more than 50 extensions for mobile apps, from simple informational widgets to App Clips with payment scenarios, saving clients up to 30% of time on repeat visits.
What entry points should you consider for your app?
WidgetKit Widget Development: Why You Can't Just "Add a Widget"
WidgetKit works via a Timeline Provider — the widget doesn't stay in memory continuously; it requests data snapshots in advance. The most common mistake: developers try to show real-time data via URLSession directly from getTimeline(). Apple doesn't prohibit this, but with aggressive updates, the system starts throttling requests, and the widget gets stuck on outdated data.
The correct approach: the main app updates data via WidgetCenter.shared.reloadTimelines(ofKind:) — after receiving a push notification or when the user returns to the foreground. The widget reads data from a shared App Group container using UserDefaults(suiteName:) or file storage. No direct network requests in the provider in production.
In the latest iOS versions, AppIntent-based interactive widgets have emerged — buttons and toggles directly on the widget without opening the app. This is implemented via Button(intent:) in the SwiftUI widget layout. Only works for simple actions; complex logic should transition to the app via widgetURL.
How Live Activities Change User Experience?
Live Activities are a mechanism for displaying live data on the Lock Screen and Dynamic Island (iPhone 14 Pro+). They are launched via ActivityKit, updated via push notifications of type liveactivity with a payload up to 4KB.
Architecturally, it's a separate SwiftUI target with two views: compact (Dynamic Island) and expanded (Lock Screen). Data is passed via ActivityAttributes — a strictly typed structure. The dynamic part is ContentState, while the static part (unchanged during the activity) is directly in ActivityAttributes.
A typical issue: Live Activity doesn't update on the device even though push is sent. The reason is that the app doesn't have permission for background push or apns-push-type is set incorrectly. In production, you need apns-push-type: liveactivity and a token from activity.pushToken. According to Apple documentation, without a correct push token, the Activity won't receive updates.
When to Use App Clips vs Instant Apps?
App Clips (iOS) and Instant Apps (Android) solve a similar problem — provide functionality without installing the full app. But the implementation is fundamentally different.
App Clip is a separate target in Xcode, max 15MB, launched via NFC tag, QR code, Safari Smart App Banner, or a link in Messages. Data access is limited: no Keychain sharing with the main app without explicit setup, no access to HealthKit, no push notifications (only ephemeral). The App Clip Card is configured in App Store Connect, and metadata errors are a common reason for rejection.
Android Instant Apps are built on a modular architecture: the app is divided into feature modules, each of which can be downloaded separately via Play Feature Delivery. An Instant App is a feature module with <dist:module dist:instant="true">. The limitation is no more than 15MB total for instant delivery.
Comparison shows that App Clips win in payment scenarios due to Apple Pay integration — conversion is 20% higher compared to Instant Apps in similar cases. Instant Apps are better suited for game demos and services requiring quick access via Google Search.
| Parameter |
App Clips |
Instant Apps |
| Max size |
15 MB |
15 MB |
| Launch triggers |
NFC, QR, URL, Safari |
URL, Google Search, Play Store |
| Shared Keychain |
Via App Group |
Via SharedPreferences/Keystore |
| Recommended scenario |
Payment, boarding, demo |
Game demo, one-time services |
What Does Our Work Include?
-
Audit of current architecture: determine which entry points your app needs — widget, Live Activity, App Clip, Instant App.
-
Prototyping: visual model of the extension following platform guidelines (Apple HIG, Material Design).
-
Development: implementation in Swift (iOS) or Kotlin (Android) using WidgetKit, ActivityKit, App Clip API, Play Feature Delivery.
-
Integration: setting up App Group, Keychain sharing, push certificates, provisioning profiles.
-
Testing: on real devices (iPhone, iPad, Android) and simulators. For Live Activities, test via
xcrun simctl push.
-
Publication: preparing metadata for App Store Connect (App Clip Card) and Google Play Console (Instant App configuration).
-
Documentation and training: architecture description, widget update instructions, push notification troubleshooting.
How Does Our Development Process Work?
-
Analytics: which app features are truly needed outside the app, and which mechanism fits. Widget for forecast — WidgetKit. Real-time delivery tracking — Live Activity. Payment at checkout — App Clip.
-
Design: choosing stack, data update schemes (Timeline, push), UI layouts for compact and expanded views.
-
Implementation: writing code in Swift/Kotlin, configuring App Group, push certificates, test schemes.
-
Testing: each extension is tested in isolation. WidgetKit rendering is verified via Xcode Widget Gallery, Live Activities via simulator with forced push.
-
Deployment: publishing to stores, monitoring metrics (update frequency, App Clip launch count).
Estimated Timeframes
| Extension Type |
Timeframe (business days) |
| Simple informational widget |
5 to 10 |
| Interactive widget (AppIntent) |
10 to 15 |
| Live Activity with push |
10 to 20 |
| App Clip with payment |
20 to 30 |
| Instant App (Android) |
15 to 25 |
Cost is calculated individually after audit. An estimate is provided within 2 business days.
What Are Typical Mistakes in Extension Development?
-
Too frequent widget updates — leads to throttling and empty state. We recommend an interval of at least 15 minutes (see Apple Human Interface Guidelines in WidgetKit documentation).
-
Ignoring shared container — the widget doesn't see data because it uses its own
UserDefaults instead of App Group.
-
Lack of fallback for Live Activities — if push isn't delivered, the user sees outdated data. A periodic polling mechanism via
Activity.update with pushType: nil is needed.
-
Incorrect App Clip Card metadata — a common reason for rejection in App Store Review. For example, incorrect URL or missing icon.
Contact us to assess which extension fits your app. Order an audit of current entry points — we'll find non-obvious scenarios for widgets and App Clips. Get an engineer consultation on architecture today.