Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
mkdir -p apk
DOCKER_BUILDKIT=1 docker build -f android/Dockerfile -o apk .
```

Note: This might take a while (from 20min up to 45-50min).

# Verify an existing APK

Expand All @@ -53,4 +53,3 @@
```shell
tools/verify-apollo.sh <path-to-verify.apk>
```

11 changes: 11 additions & 0 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ follow [https://changelog.md/](https://changelog.md/) guidelines.

## [Unreleased]

## [55.8] - 2026-04-15

### FIXED

- Edge to edge support on certain screens
- Emergency kit key rotation when adding a new recovery code
- Biometrics support for Android < 9
- Submarine swap being created twice on activity reconstruction
- Reproducible builds
- Various crash fixes

## [55.7] - 2026-03-25

### ADDED
Expand Down
71 changes: 71 additions & 0 deletions android/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# CLAUDE.md

This directory contains Muun's self-custody wallet implementation for android (aka called Apollo).

# Apollo

## Overview
Apollo is the android app implementation of Muun's self-custody wallet for Bitcoin & Lightning. It enables users to send/receive btc over mainnet and lightning networks.

## Architecture

### Original architecture (what you will find in most of the codebase)
- **Architecture pattern**: Clean architecture (data/domain/presentation) & MVP (model-view-presenter)
- **Language**: Java
- **Reactive framework**: RxJava 1.X
- **Object oriented programming**: Heavy dependency on inheritance
- **UI framework**: Android view system (aka XMLs)

### Target architecture (where we are working towards)
- **Architecture pattern**: Clean architecture (data/domain/presentation) & MVVM (model-view-viewmodel)
- **Language**: Kotlin
- **Reactive framework**: RxJava 1.X
- **Object oriented programming**: Avoid inheritance hell (favor composition over inheritance), top-level/extension functions for sharable behaviour
- **UI framework**: Android view system (aka XMLs)

## Libwallet
Libwallet is a shared library written in Golang that contains domain logic shared between Apollo and Falcon (Muun's self-custody wallet implementation for iOS). Module :libwallet contains the .aar generated while building the libwallet project located at ../libwallet. Apollo & Libwallet communication is made through gRPC.

## Commands
```bash
./tools/libwallet-android.sh # MUST run before first build
./gradlew :android:apolloui:assembleLocalDebug
./gradlew :android:apolloui:assembleDogfoodDebug
```

## Token Economics

**CRITICAL**: All file operations must be token-efficient:

- **Be concise**: Remove verbosity, keep technical accuracy
- **Avoid repetition**: Don't repeat context already established
- **No code examples in CLAUDE.md**: All examples belong in REFERENCES.md
- **Direct communication**: Skip filler phrases
- **Efficient updates**: Only modify necessary sections
- **Prefer references**: Link to existing examples instead of duplicating

**Apply to all files**: Code, documentation, comments, commit messages, AI responses.

## Code Style
- IMPORTANT: All new code MUST be Kotlin (not Java)
- NEVER use ButterKnife (@BindView); prefer ViewBinding
- YOU MUST use MVVM for new screens (not MVP)
- ViewModels: StateFlow for continuous state, SharedFlow for one-time events

## Code Quality
- Pre-commit hook runs Checkstyle/linters on all commits (style, imports, line length, whitespace)
- YOU MUST fix all linter errors before committing (hook will fail otherwise)
- Common issues: unused imports, lines >100 chars, whitespace, brace style
- See @android/ai-rules/SKILLS.md for full pre-commit hook details and how to fix failures

## Documentation
- **SKILLS.md** - Debugging, testing, code review, refactoring, performance, security (@android/ai-rules/SKILLS.md)
- **LEARNINGS.md** - Common mistakes, deprecation history, codebase gotchas (@android/ai-rules/LEARNINGS.md)
- **FEEDBACK.md** - How to present code changes, errors, options, progress (@android/ai-rules/FEEDBACK.md)
- **REFERENCES.md** - MVVM, AsyncAction, migrations, custom views, RecyclerView, navigation (@android/ai-rules/REFERENCES.md)

YOU MUST **proactively** update these files when you discover new patterns, gotchas, or better presentation techniques — do it immediately as part of the task, don't wait to be asked. This is how knowledge persists across sessions.

## Critical: always read files completely

When instructed to read a file as input (e.g. deployment_input.md), read the ENTIRE file using multiple Read calls with offset/limit if needed. Never start analyzing until every line has been read.
100 changes: 100 additions & 0 deletions android/ai-rules/FEEDBACK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Apollo Feedback Guide

## Reporting Technical Decisions

**Format:**
```
I'm using [PATTERN] because [REASON].

Example implementation: [FILE] in [CLASS/FUNCTION]
```

**Example:**
```
I'm using StateFlow instead of LiveData because the codebase is migrating to Coroutines Flow.

Reference: NfcReaderViewModel.kt (viewState property)
```

## Presenting Code Changes

**DO:**
- Use file and function/class references (e.g., `MyActivity.kt in onCreate()`)
- Show only changed sections, not entire files
- Explain WHY, not just WHAT
- Link to reference implementations in codebase

**DON'T:**
- Dump entire file contents
- Explain basic Kotlin/Android concepts (assume user knows)
- Over-explain trivial changes

## Error Reporting

**Build errors:**
```
Build failed at [TASK]:
[ERROR MESSAGE]

Likely cause: [HYPOTHESIS]
Fix: [ACTION]
```

**Runtime errors:**
```
Crash in [ACTIVITY/VIEWMODEL]:
[STACK TRACE - relevant lines only]

Root cause: [ANALYSIS]
Fix: [CHANGES MADE]
```

## Asking for Clarification

**When unclear:**
- Architecture decision (MVVM vs MVP for specific case)
- UX behavior (navigation flow, error handling)
- Data model changes (affects DB schema)

**DON'T ask about:**
- Code style (follow existing patterns)
- Naming conventions (follow codebase conventions)

## Presenting Options

**Format:**
```
Option 1: [APPROACH]
Pros: [...]
Cons: [...]
Example: @path/to/reference

Option 2: [APPROACH]
Pros: [...]
Cons: [...]
Example: @path/to/reference

Recommendation: Option [X] because [REASON]
```

## Progress Updates

**For multi-step tasks:**
1. List steps upfront
2. Mark completed steps
3. Report blockers immediately
4. Summarize at end

**Example:**
```
Completed:
✓ Created ViewModel with ViewState/ViewCommand
✓ Created Activity with ViewBinding
✓ Added English strings

In progress:
→ Adding Spanish translations

Pending:
- Testing navigation flow
```
108 changes: 108 additions & 0 deletions android/ai-rules/LEARNINGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Apollo Learnings & Gotchas

## Common Mistakes

**ViewState location:**
- ❌ Separate file: `sealed interface ViewState` in ViewState.kt
- ✅ Inside ViewModel: `sealed interface ViewState` inside MyViewModel.kt

**Activity base class:**
- ❌ Extending BaseActivity (deprecated)
- ❌ Extending ExtensibleActivity (deprecated)
- ✅ Extending AppCompatActivity directly

**View binding:**
- ❌ `private lateinit var binding: MyBinding`
- ✅ `private val binding by lazy { MyBinding.inflate(layoutInflater) }`

**ViewModel instantiation:**
- ❌ `private val viewModel = MyViewModel()` (no DI)
- ✅ `private val viewModel: MyViewModel by viewModels()` (Dagger)

**Flow collection:**
- ❌ `viewModel.state.collect {}` in onCreate (leaks)
- ✅ `lifecycleScope.launch { repeatOnLifecycle(STARTED) { viewModel.state.collectLatest {} } }`

**ButterKnife → ViewBinding migration:**
- Remove `tools:viewBindingIgnore="true"` from layout XML
- Remove `@BindView` annotations and butterknife imports
- Add `bindingInflater()` override returning lambda with `MyBinding::inflate`
- Keep `getLayoutResource()` returning 0 with TODO comment (abstract method, will be removed after full migration)
- Access views via `binding.viewId` instead of direct field reference
- DON'T remove `getLayoutResource()` (causes compile error, it's abstract in BaseFragment)

## Deprecation History

**Why BaseActivity is deprecated:**
- Massive god class with 50+ methods
- Tight coupling to MVP pattern
- Hard to test, hard to understand
- Blocks migration to MVVM

**Why P2P/Contacts is deprecated:**
- Feature never gained traction
- Maintenance burden too high
- Focus shifting to core wallet functionality
- Will be removed in Q2 2026

**Why ButterKnife is deprecated:**
- ViewBinding is official Android solution
- Better null safety, compile-time checking
- No reflection overhead

## Codebase Gotchas

**Flavor-specific behavior:**
- Certificate pins differ per flavor (@android/apolloui/houston.gradle)
- Local uses localhost:8080, regtest uses remote, prod uses api.muun.com
- DON'T hardcode URLs, use BuildConfig

**Database migrations:**
- SQLDelight migrations are NOT auto-applied
- MUST add migration in @data/db/migrations/ (next number in sequence)
- Test migration with `./gradlew :apolloui:verifySqlDelightMigration`

**Libwallet changes:**
- If you change @libwallet/, MUST rebuild: `./tools/libwallet-android.sh`
- Changes won't reflect until rebuilt and gradle sync'd
- Takes ~5 minutes to build

**MuunHeader:**
- Custom view for consistent header across app
- Use `binding.header.attachToActivity(this)` in Activity.onCreate
- Navigation modes: BACK, NONE, EXIT
- Reference: @android/apolloui/src/main/java/io/muun/apollo/presentation/ui/view/MuunHeader.java

## Crashlytics Investigation

**Always pull at least 10 events across all variants:**
- 2 events gave incomplete picture (ANR appeared low-end-only, but Pixel 7a was affected too)
- More events reveal patterns: device diversity, processState, OS version clustering
- Use `crashlytics_batch_get_events` with sample events from all variants, at least 10 total
- Prioritize events from different variants for maximum diversity

**Device fingerprint red flags:**
- `locale: UNSET` + `bigQueryPseudoId: null` → likely automated/non-legitimate environment
- `installSource-initiatingPackage: com.miui.huanji` → Xiaomi phone migration tool (copies SharedPreferences but NOT Android Keystore)

## Android/Kotlin Gotchas

**SharedFlow vs StateFlow:**
- StateFlow: Continuous state (always has current value)
- SharedFlow: One-time events (navigation, toasts, errors)
- DON'T use StateFlow for one-time events (will replay on config change)

**repeatOnLifecycle:**
- STARTED: Collect while visible (correct for most UI updates)
- RESUMED: Collect while focused (use for analytics)
- CREATED: Collect while alive (leaks, don't use)

**ViewBinding naming:**
- activity_my_screen.xml → ActivityMyScreenBinding
- fragment_my_screen.xml → FragmentMyScreenBinding
- view_my_widget.xml → ViewMyWidgetBinding

**Kotlin scope functions:**
- Use `apply` when calling multiple methods on same object (avoid repetition)
- ❌ `binding.header.attachToActivity(this); binding.header.setTitle(...)`
- ✅ `binding.header.apply { attachToActivity(this@onCreate); setTitle(...) }`
Loading
Loading