Skip to main content

Welcome Contributors!

AnkiDroid Companion is an open-source project that welcomes contributions from the community. Whether you’re fixing bugs, adding features, improving documentation, or suggesting enhancements, your help is appreciated!

Getting Started

Prerequisites

Before you begin, ensure you have:
  • Android Studio (latest stable version recommended)
  • JDK 8 or higher
  • Android SDK with API level 26+ (Android 8.0 Oreo)
  • Git for version control
  • AnkiDroid installed on a test device or emulator

Development Setup

  1. Clone the repository:
    git clone https://github.com/unalkalkan/AnkiDroid-Companion.git
    cd AnkiDroid-Companion
    
  2. Open in Android Studio:
    • Launch Android Studio
    • Select “Open an Existing Project”
    • Navigate to the cloned repository
    • Wait for Gradle sync to complete
  3. Configure AnkiDroid on your test device:
    • Install AnkiDroid from Google Play or F-Droid
    • Create at least one deck with some cards
    • Ensure AnkiDroid’s API is enabled (it’s enabled by default)
  4. Build and run:
    ./gradlew assembleDebug
    
    Or use Android Studio’s “Run” button (Shift+F10)
  5. Grant permissions: When you first launch the app, it will request the READ_WRITE_DATABASE permission. Grant this to allow API access.

Project Structure

AnkiDroid-Companion/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/ankidroid/companion/
│   │   │   │   ├── MainActivity.kt              # Main UI activity
│   │   │   │   ├── AnkiDroidHelper.java         # API integration
│   │   │   │   ├── Notifications.kt             # Notification display
│   │   │   │   ├── NotificationReceiver.kt      # Button click handler
│   │   │   │   ├── PeriodicWorker.kt           # Background worker
│   │   │   │   ├── CardInfo.java               # Card data model
│   │   │   │   └── StoredState.java            # State data model
│   │   │   ├── res/
│   │   │   │   ├── layout/                     # UI layouts
│   │   │   │   └── values/                     # Strings, colors, etc.
│   │   │   └── AndroidManifest.xml
│   │   └── test/                               # Unit tests (future)
│   └── build.gradle.kts                        # App dependencies
├── gradle/
├── build.gradle.kts                            # Project config
└── README.md

Build Configuration

From build.gradle.kts:6-50:
android {
    namespace = "com.ankidroid.companion"
    compileSdk = 34
    
    defaultConfig {
        applicationId = "com.ankidroid.companion"
        minSdk = 26    // Android 8.0+
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }
    
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    
    kotlinOptions {
        jvmTarget = "1.8"
    }
}
The app targets Android 8.0 (API 26) and above, which covers ~95% of active Android devices as of 2024.

Development Workflow

Making Changes

  1. Create a feature branch:
    git checkout -b feature/your-feature-name
    
    Or for bug fixes:
    git checkout -b fix/issue-description
    
  2. Make your changes:
    • Write clean, readable code
    • Follow existing code style
    • Add comments for complex logic
    • Test thoroughly on a real device
  3. Test your changes:
    • Run the app on a physical device (emulator may have permission issues)
    • Test with different deck configurations
    • Verify notifications appear correctly
    • Test background worker behavior
    • Check that card reviews sync to AnkiDroid
  4. Commit your changes:
    git add .
    git commit -m "Add feature: description of your changes"
    
    Write clear commit messages:
    • Use present tense (“Add feature” not “Added feature”)
    • Be concise but descriptive
    • Reference issue numbers if applicable
  5. Push to your fork:
    git push origin feature/your-feature-name
    

Submitting a Pull Request

  1. Push your branch to GitHub
  2. Open a pull request on the main repository
  3. Describe your changes:
    • What problem does this solve?
    • How did you test it?
    • Are there any breaking changes?
    • Screenshots/videos if UI changes
  4. Wait for review:
    • Maintainers will review your PR
    • Address any requested changes
    • Be patient and responsive to feedback

Code Style Guidelines

Example:
class NotificationHelper {
    private val channelId = "anki_channel"
    
    fun showCardNotification(card: CardInfo) {
        // Implementation
    }
    
    companion object {
        const val MAX_RETRY_COUNT = 3
    }
}

Known Limitations & Improvement Opportunities

From README.md:36-44, here are areas that need improvement:

1. Minimalistic Card Support

Current Limitation: The app only supports simple cards with plain text. Complex HTML, images, audio, and formatting are not rendered. Code Location: Notifications.kt:28-40 Current Implementation:
val questionText = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    Html.fromHtml(card.q, Html.FROM_HTML_MODE_LEGACY).toString()
} else {
    @Suppress("DEPRECATION")
    Html.fromHtml(card.q).toString()
}
Improvement Ideas:
  • Render HTML in a WebView within the notification
  • Support image display in expanded notifications
  • Add audio playback buttons for cards with sound
  • Handle cloze deletions properly
  • Support LaTeX/MathJax rendering
Good First Issue: Improve HTML rendering by preserving basic formatting (bold, italic, underline) instead of stripping all HTML.

2. Skipping Cards

Current Limitation: No way to skip a card without answering it. Improvement Ideas:
  • Add a fifth “Skip” button to notifications
  • Implement card burial (hide until next day)
  • Add “Suspend” option to remove card from rotation
Implementation Suggestion: Modify Notifications.kt:49-52 to add a skip button:
expandedView.setOnClickPendingIntent(R.id.button1, createIntent(context,"ACTION_BUTTON_1"))
expandedView.setOnClickPendingIntent(R.id.button2, createIntent(context,"ACTION_BUTTON_2"))
expandedView.setOnClickPendingIntent(R.id.button3, createIntent(context,"ACTION_BUTTON_3"))
expandedView.setOnClickPendingIntent(R.id.button4, createIntent(context,"ACTION_BUTTON_4"))
expandedView.setOnClickPendingIntent(R.id.buttonSkip, createIntent(context,"ACTION_SKIP")) // New
Then handle in NotificationReceiver.kt:14-19:
when (intent?.action) {
    "ACTION_BUTTON_1" -> respondCard(context, AnkiDroidHelper.EASE_1)
    "ACTION_BUTTON_2" -> respondCard(context, AnkiDroidHelper.EASE_2)
    "ACTION_BUTTON_3" -> respondCard(context, AnkiDroidHelper.EASE_3)
    "ACTION_BUTTON_4" -> respondCard(context, AnkiDroidHelper.EASE_4)
    "ACTION_SKIP" -> skipCard(context) // New handler
}

3. New Study Interval

Current Limitation: Hard-coded 8-hour interval for checking new cards. Code Location: MainActivity.kt:47-50
val periodicWorkRequest = PeriodicWorkRequest.Builder(
    PeriodicWorker::class.java,
    8, TimeUnit.HOURS  // Hard-coded
).build()
Improvement Ideas:
  • Add a settings screen
  • Allow users to configure check interval (1-24 hours)
  • Store preference in SharedPreferences
  • Update worker when preference changes
Implementation Suggestion:
// In SettingsActivity.kt (new file)
class SettingsActivity : AppCompatActivity() {
    private fun saveInterval(hours: Int) {
        val prefs = getSharedPreferences("app_settings", Context.MODE_PRIVATE)
        prefs.edit().putInt("check_interval_hours", hours).apply()
        
        // Reschedule worker with new interval
        schedulePeriodicWorker(hours)
    }
}

// In MainActivity.kt
private fun getCheckInterval(): Long {
    val prefs = getSharedPreferences("app_settings", Context.MODE_PRIVATE)
    return prefs.getInt("check_interval_hours", 8).toLong()
}
Good First Issue: Create a settings screen with a number picker for check interval.

4. Embedded Strings

Current Limitation: Many strings are hard-coded in the code instead of in res/values/strings.xml. Examples:
// MainActivity.kt:34
val name = "AnkiNotificationChannel"  // Should be in strings.xml

// Notifications.kt:43
collapsedView.setTextViewText(R.id.textViewCollapsedTitle, "Anki • $deckName")

// Notifications.kt:64
collapsedView.setTextViewText(R.id.textViewCollapsedHeader, 
    "Congrats! You've finished the deck!")  // Hard-coded
Improvement: Move all user-facing strings to res/values/strings.xml:
<resources>
    <string name="notification_channel_name">AnkiNotificationChannel</string>
    <string name="notification_title_format">Anki • %1$s</string>
    <string name="deck_finished_header">Congrats! You\'ve finished the deck!</string>
    <string name="deck_finished_content">New notifications will arrive when it\'s time to study!</string>
</resources>
Then reference in code:
val name = getString(R.string.notification_channel_name)
collapsedView.setTextViewText(
    R.id.textViewCollapsedHeader, 
    getString(R.string.deck_finished_header)
)
Good First Issue: Extract all hard-coded strings to strings.xml for easier localization.

5. Future Features

From README.md:16: Planned Expansions:
  • Home screen widget: Quick access to study without opening notification shade
  • Lock screen widget: Study while phone is locked
  • Statistics view: Track daily/weekly review counts
  • Multiple deck support: Rotate between multiple decks
  • Custom notification layouts: User-configurable button labels and colors

Reporting Issues

Before Opening an Issue

  1. Search existing issues to avoid duplicates
  2. Update AnkiDroid to the latest version
  3. Check if permissions are granted in Android settings
  4. Test on a physical device (emulators may behave differently)

Opening a Bug Report

Include:
  • Device information: Make, model, Android version
  • AnkiDroid version: Check in AnkiDroid’s About screen
  • App version: Check in app settings or build.gradle.kts
  • Steps to reproduce: Detailed steps to trigger the bug
  • Expected behavior: What should happen
  • Actual behavior: What actually happens
  • Logs: Use adb logcat to capture relevant logs
  • Screenshots/videos: If applicable
Example:
**Device:** Pixel 6, Android 14
**AnkiDroid:** v2.17.6
**Companion:** v1.0 (commit abc123)

**Steps to reproduce:**
1. Open app and select "Japanese" deck
2. Click Refresh button
3. Notification appears
4. Click "Good" button

**Expected:** Next card should appear
**Actual:** Notification disappears completely

**Logs:**
E/NotificationReceiver: NullPointerException at line 28

Opening a Feature Request

Describe:
  • What feature you’d like to see
  • Why it would be useful: Use cases, user stories
  • How it might work: Proposed UI/UX or implementation
  • Related features: Similar apps or AnkiDroid features

Testing

Manual Testing Checklist

Before submitting a PR, test these scenarios:
  • App launches successfully
  • Permission request appears on first launch
  • Deck list populates correctly
  • Clicking “Refresh” shows notification
  • Notification displays question and answer
  • All four buttons work (Again, Hard, Good, Easy)
  • Next card appears after answering
  • Completion notification shows when deck is finished
  • Periodic worker runs after 8 hours (test by changing interval)
  • App works after device restart
  • State persists across app restarts
  • Works with renamed decks
  • Works with multiple decks (switching between them)

Testing with ADB

View logs:
adb logcat | grep -E "(Notifications|BackgroundService|AnkiDroidHelper)"
Trigger background worker manually:
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED
Clear app data (reset state):
adb shell pm clear com.ankidroid.companion
Check SharedPreferences:
adb shell run-as com.ankidroid.companion cat /data/data/com.ankidroid.companion/shared_prefs/com.ichi2.anki.api.state.xml

License

From README.md:52-54: This project is licensed under the MIT License. By contributing, you agree that your contributions will be licensed under the same license.

Getting Help

If you need help:
  1. Check existing documentation in this wiki
  2. Read the source code: It’s well-commented
  3. Open a discussion: For questions about implementation
  4. Ask in issues: Tag with “question” label

Recognition

Contributors will be:
  • Listed in the GitHub contributors page
  • Mentioned in release notes for significant contributions
  • Thanked in the project README

Thank you for contributing to AnkiDroid Companion! Every improvement, no matter how small, makes the app better for everyone. Happy coding!

Build docs developers (and LLMs) love