Development Guide - ankidroid/Anki-Android GitHub Wiki

Getting Started

We always welcome any contributors, and you can start contributing immediately if you desire so. Feel free to join our Discord: #dev-ankidroid to join the community, or if you need help with the below. We are all volunteers.

This page contains information on how you can contribute back to the AnkiDroid project. You do not have to read all of it, just come back when you need an answer to a commonly asked question.

Table of Contents


Getting Started

Community

Feel free to join our Discord Server: Anki to join our community. This is shared between the Anki Desktop app and Mobile apps

A few relevant channels are:

  • #mobile-apps - Help for using AnkiDroid and AnkiMobile
  • #dev-ankidroid - General AnkiDroid Development discussions
  • #ankidroid-gsoc - Discussions/introductions related to onboarding and the Google Summer of Code program
  • #help - General Anki help

If it's unclear exactly what needs to be done, or how to do it, please don't hesitate to ask for clarification or help, ensuring that you skim our guides beforehand to see if your question has already been asked.

Issues to get started with

If you are a new developer looking to contribute something to AnkiDroid, please take a look and see if there's anything that you'd like to work on in the issue tracker.

The "Good First Issue" label has been added to any tasks that seem like a good way to get started working with the codebase.

If you are looking to make a lasting impact on the project from the get-go, issues with the label "Help Wanted" are tasks that we'd really like to have done but don't yet have time to do ourselves.

Please let us know that you're working on an open issue and let us assign it to you, this ensures that two people aren't working on the same issue, and ensures that all effort is valuable to the project. If an issue hasn't seen activity in a couple of weeks, feel free to ping the assignee to see if they're working on it, or ask a maintainer to reassign the issue to you.

If you can't work on an issue any more, please post another message to let us know.

If it's unclear exactly what needs to be done, or how to do it, please don't hesitate to ask for clarification or help!

Common tasks to get started

Adding code documentation is helpful, and easy to get started with. This includes adding Javadocs to classes, members, and methods, and adding annotations such as @CheckResult. It also include cleaning nullability typing, removing ? in type when it's clear from context that types can not actually be null (e.g. the argument is always used with !!)

Increasing test coverage is extremely welcomed, as it will considerably improve the future of the codebase.

Submitting feature requests

If you want to create your own feature, please post a feature request via an issue so that the core contributors can confirm whether it would be accepted.

Source code

First, register here on GitHub, and follow the instructions on GitHub on the Anki-Android repository to fork and clone the code. If you want to be notified about each new improvement/bugfix, please subscribe to the commits feed for the main branch.

To ensure that users do not update the dependency or the Gradle plugin on their own, you can provide clear instructions and guidance in your documentation. Here's a revised version of your instructions that emphasizes this point:


Android Studio

The next step is to install Android Studio and the Android SDK. Open Android Studio and choose "Open Project," then select the folder where you earlier cloned the Github repository (we will refer to this folder as %AnkiDroidRoot%).

On opening the project, it should start to build, and eventually, it may prompt you to install missing SDK components. Please do not update any dependencies or the Gradle plugin manually. This is taken care of by the project maintainers and automated bots to ensure compatibility and stability.

If prompted, install the following missing SDK components one by one:

  • Android SDK Build-tools and Android SDK platform (ensure the version matches the "compileSdk" in the project's build.gradle file).
  • Android Support Repository (latest version)
  • Android Support Library (latest version)

After installing these dependencies as prompted, the project should build successfully.

If you get an error like The project is using an incompatible version (AGP 8.2.0-rc01) of the Android Gradle plugin. Latest supported version is AGP 8.1.2, then you should check if you have a new enough release of Android Studio. If you encounter any other issues related to dependencies or the Gradle plugin, please report them to the project maintainers/discord.

By following these instructions and avoiding manual updates, you'll help ensure a stable and reliable development environment for the project.

Installing an emulator for testing

Provided you are able to use hardware acceleration for the Android emulators, setting up an emulator is a very effective method for testing code and running the connected android tests. If possible, verify your work periodically against both the newest version of the emulator available and the oldest version we support that you can run (at this time API18 is the oldest emulator easy to run though it is possible to run all the way down to API15 on linux at least with some effort).

Connecting your device to Android studio via adb to use for testing

In order to run a custom build of AnkiDroid on your device or attach the debugger, you'll need to connect your device over the adb protocol. Open your Android device, and enable developer options and USB debugging. Finally when you connect your device to your computer over USB, it should get recognized by Android Studio and you should start to see output from it in the logcat section of Android studio.

If you don't want to test on your actual collection, you just have to rename the AnkiDroid folder to any other name, such as AnkiDroidBack and restart AnkiDroid. Since AnkiDroid won't find an AnkiDroid folder, it will create one and assume it's a new collection. Note however that it will still have your AnkiWeb login and some preference settings. When you are done testing, you can rename this folder AnkiDroidBack to AnkiDroid.

Running AnkiDroid from within Android studio

Connect your device to your computer, or setup an emulator, then select Run -> Run 'AnkiDroid' from the menu in Android Studio. This will compile a version of AnkiDroid suitable for testing (i.e. signed with a debug key), and pop up a window where you can select the device or emulator that you want to install and run the code on. See the main Android developer documentation for more detailed information.

Make sure that your Run Configuration for the AnkiDroid Project is set to use the APK from the app bundle instead of the Default APK (Run -> Edit Configurations -> Deploy (select 'APK from app bundle').

An apk file signed with a standard "debug" key will be generated named "AnkiDroid-debug.apk" in: %AnkiDroidRoot%/AnkiDroid/build/outputs/apk/

AnkiDroid source code overview: Where to find what

  • /AnkiDroid/src/main/ is the root directory for the main project (%root)
  • %root/java/com/ichi2/anki/ is the directory containing the main AnkiDroid source code.
  • %root/java/com/ichi2/anki/DeckPicker.kt is the code which runs when starting AnkiDroid from the Android launcher.
  • %root/java/com/ichi2/libanki/ contains the underlying Anki code which is common to all Anki clients. It is ported directly from the Anki Desktop python code into Java.
    • Because libanki code comes upstream from the common Anki code, edit it only when necessary. Keeping the code consistent with the common core makes it easier to integrate future upgrades.
  • %root/assets/ contains the flashcard.css and HTML templates included with each flash card
  • %root/res/ contains the Android resource XML files for the project.
  • %root/res/values/ contains app strings, whiteboard colors, and a basic HTML template for flashcards.
  • %root/res/layout/ contains the GUI layouts for most screens.
  • %root/res/drawable-****/ contains the icons used throughout the app at various resolutions.

Anki Desktop pages

With our bridge to Anki's backend, we can display the same Svelte pages present in the desktop version. This also means that most changes to an Anki page are expected to be made upstream.

You can find them in the ts directory of the Anki repository. They are automatically exported into HTML pages by the backend bridge and accessible at web/pageName.html (the name corresponds to each subdirectory in the ts folder of Anki).

To handle POST requests in Anki pages, we use a custom NanoHTTPD server because WebViews can't intercept POST requests in Android. Additionally, if you are in a DEBUG build, you can inspect the HTML page in your browser (chrome:inspect in Chromium-based browsers).

Currently, com.ichi2.anki.pages is the package containing the Anki pages that are being reused in AnkiDroid.

Submit improvements

Once you have improved the code, commit it and send a pull request to AnkiDroid Github Repository. It will then be accepted after the code has been reviewed, and the enhanced application will be available on the Android Market on the next release. See the branching model section if you are unsure which branch to push to.

If you have trouble with Git, you can paste the changed files as text to the forum or github issue tracker.

Git workflow

Git can be a bit complicated to use in the beginning. This section describes the workflow that we recommend for regular contributors. The following assumes that you're using either linux, or the linux subsystem for Windows. We also assume you're using SSH to authenticate with github (highly recommended) and that you've already forked the AnkiDroid repository on github.

Initial setup (one time)

First let's set up our local repository

git clone [email protected]:GITHUB_USERNAME/Anki-Android.git AnkiDroid
cd AnkiDroid
git remote add upstream https://github.com/ankidroid/Anki-Android.git

Making a new pull request

Now if want to make a new change to the code base, we create a new 'feature branch' based off the latest version of the main branch:

git checkout main
git pull upstream main
git checkout -b my-feature-branch 
# make your changes to the source code
git push origin HEAD

On navigating to the main AnkiDroid repository, github will now pop up a message asking you if you want to create a new pull request based on the branch that you just pushed.

Dealing with merge conflicts

If changes are made to the AnkiDroid repository that conflict with the changes in your pull request in-between creating the feature branch and your changes getting merged into the main repository, it may be necessary to rebase your code:

git checkout main
git pull upstream main
git checkout my-feature-branch # or just "git checkout -" to save typing
git rebase main
# it may be necessary to resolve merge conflicts here
# if you need to update the existing pull request, you should do a 'force' push
git push origin HEAD -f

Running automated tests

Prerequisites

Before running automated tests, it is essential to have the appropriate JDK installed. For the latest version of Anki-Android-Backend (0.1.15), it has been confirmed that JDK 21 works seamlessly.

AnkiDroid has two types of test: unit tests and on-device integration tests. Integration tests require either a physical device or emulator. You can run all tests from the command line with

./gradlew jacocoTestReport

This typically takes between 5 and 15 minutes. If it seems like a test has hung, execute ./gradlew clean build and try again. The first run will be longer as test-only dependencies need to be downloaded

Note that running ./gradlew jacocoTestReport will run the unit tests, and then run a connected check on every currently running emulator and connected device. If you have a series of emulators that contain NEW in their name (that is, if you run emulator -list-avds you see NEW in the AVD name) then you may appreciate scripts that will start and stop all your NEW emulators at once - https://github.com/ankidroid/Anki-Android/blob/main/tools/quality-check/start_all_emulators.sh and https://github.com/ankidroid/Anki-Android/blob/main/tools/quality-check/stop_all_emulators.sh will do this for you.

Unit tests

There are unit tests defined in the AnkiDroid/src/test directory, with an extendable test superclass available using the Robolectric framework to make standard Android services available, including sqlite so you can operate on Anki Collections in your tests - each Collection created on the fly prior to each test method and deleted afterwards for isolation. You can run these tests by selecting them directly from AndroidStudio for individual tests or all tests from one file, or you can run them from the command line and generate a coverage report to verify the effect of your testing from the command line using:

./gradlew jacocoUnitTestReport

Afterwards you should find the coverage report in %AnkiDroidRoot%/AnkiDroid/build/reports/jacoco/jacocoUnitTestReport/html/index.html

Run single unit test

Change com.ichi2.anki.NoteEditorTest with test class name.

./gradlew AnkiDroid:testPlayDebugUnitTest --tests com.ichi2.anki.NoteEditorTest

Afterwards you should find the coverage report in %AnkiDroidRoot%/AnkiDroid/build/reports/tests/testPlayDebugUnitTest/index.html

On-device integration tests

In addition to the unit tests, several integration tests are defined in the AnkiDroid/src/androidTest folder. You can run the tests from within Android Studio by simply right clicking on the test and running it against the chosen connected Android device (be sure to choose the icon with the Android symbol if there are multiple options shown), or from the command line against all connected devices at once using

./gradlew jacocoAndroidTestReport

After this you should find a coverage report that integrates unit and integration test execution in %AnkiDroidRoot%/AnkiDroid/build/reports/jacoco/jacocoTestReport/html/index.html

Note: Some of the connected tests involve the deletion of models, which will force a full-sync, so it's not recommended to try running the tests on your main device.

Lint

AnkiDroid defines custom lint rules to enforce code conventions and ensure that bugs are not introduced. We run these for every pull request on the Code Quality Checks / Lint Release (pull_request) check.

To run these manually, open a terminal (either in Android Studio, or in %AnkiDroidRoot%) and execute:

./gradlew lintRelease

Ktlint

AnkiDroid uses Ktlint for ensuring coding style and standard for Kotlin files. Every pull request runs the Ktlint check.

To run it manually, open a terminal (either in Android Studio, or in %AnkiDroidRoot%) and execute:

./gradlew ktlintCheck

To format Kotlin files, run the following command in the terminal:

./gradlew ktlintFormat

Ensure that you run this command for formatting before sending a pull request.

Troubleshooting step

If tests do not behave as expected, you can replace ./gradlew by ./gradlew clean to clean the directory before running test.

If on-device tests are slow or fail, it maybe because of conflict with your ankidroid collection. To avoid them, you can simply rename the "ankidroid" folder to "ankidroid_back" on your device when running test. You can then rename "ankidroid_back" to "ankidroid" when you are done

Compiling from the command line

If you have the Android SDK installed, you should be able to compile from the command line even without installing Android Studio.

Windows: Open a command prompt in %AnkiDroidRoot% and type:

gradlew.bat assembleDebug

Linux & Mac: Open a terminal in %AnkiDroidRoot% and type:

./gradlew assembleDebug

An apk file signed with a standard "debug" key will be generated named "AnkiDroid-debug.apk" in: %AnkiDroidRoot%/AnkiDroid/build/outputs/apk/

Continuous Integration

Our GitHub Actions workflows are defined in: https://github.com/ankidroid/Anki-Android/tree/main/.github/workflows

And accessible: https://github.com/ankidroid/Anki-Android/actions

Debugging hanging unit tests (output.bin)

Our JUnit Tests output a logcat-{platform}-1 artifact on failure. If the build times out, this will only contain an output.bin file

To find the flaky test, install ripgrep if required, and execute:

strings output.bin | rg '(\-\- .*)' -or '$1' | cut -c 14- | sort | uniq -u

The name of the hung test will be output: test "test_AddNewTag_newHierarchicalTag_willUniformHierarchicalTag"

Adding translations

AnkiDroid uses string resources for all translatable strings. These are managed by a platform called CrowdIn and Pontoon

<string name="example_key">Example value</string>
  1. Determine the correct resource XML file to edit (Guide) and expand it in Android Studio
  2. Add the string to the first file in the list (/values) and no other file.
  3. Reference the string using the appropriate function
  4. Ignore any errors stating that these are missing translations after adding the string. If it compiles, it's fine. Maintainers will handle generating the strings after the pull request is completed.
  5. Take a screenshot of the string in context and add it to the Pull Request
  6. After the change is merged, a maintainer should add this screenshot to the platform to help translators with context
Example of the correct file to modify

demo

Translation Conventions

  • Avoid ending a string with a full stop unless there are multiple sentences
  • Plurals should use <plurals. Only define one and other for English as CrowdIn handles other plural types
  • Keys names should be lowercase using underscores
  • Values should be unique in English. If the values are not unique, a comment="" attribute must be added to explain the differences to our translators. This is enforced by lint.

Handling translations

As described in the contributing wiki, AnkiDroid localization is done through the Crowdin and Pontoon platform. Developers should basically ignore all resource folders that have non-English locales. Edit the English strings only, and one of the project owners will handle the syncing of the translations. The process works as follows:

  • Developers can freely add, delete, or modify strings in English to the resources folder (values/) and commit to git.
    • Renaming translation keys should be avoided if at all possible if the use is the same as it causes re-translation (appears as a delete-old/add-new to translators).
    • If you update the value of an existing key it will not be marked as needing translation. Existing translations are preserved. If you need to change a string significantly, meaning it needs re-translation, you must use a new key and remove the old key.
  • A project owner will run a script that pushes those changes to the crowdin and pontoon platform .
  • Translators will see any new untranslated strings on crowdin and pontoon and submit their translations
  • Before release a project owner will run another script which pulls all the current translations from crowdin and pontoon and overwrites the existing files
  • These new files are then committed and pushed to GitHub

Making "parallel" builds

If you want to run several different versions of AnkiDroid side by side (e.g. as described in the FAQ on using profiles), you need to edit the package ID (from com.ichi2.anki) in the following places so that every version of AnkiDroid that you install has a unique ID:

  • applicationId in AnkiDroid/build.gradle
  • applicationIdSuffix in AnkiDroid/build.gradle

There are some convenient gradle command arguments that automates this process, which you can run from a shell from the top level AnkiDroid directory as follows:

./gradlew assemblePlayRelease -PcustomSuffix="suffix" -PcustomName="New app name"

Anki database structure

See the separate wiki page for a description of the database structure

Branching Model

See the Release Procedure Wiki Page

Localization Administration

Updating the main strings from Git to Crowdin is a pretty delicate thing. Uploading an empty string.xml for instance would delete all translations. And uploading changed strings delete as well all translations. This is the desired behavior in most cases, but when just some English typos are corrected this shouldn't destroy all translations.

In this case, it's necessary to:

  1. rebuild a download package at first (option "r" in script [currently broken])
  2. download all translations (update-translations.py)
  3. upload the changed strings
  4. reupload the translations (option "t" and language "all").

Code Coverage

In march 2021, only 36% of the code were covered. That is that all of our test only runs 36% of the lines written. Erroneous change in the remaining 64% of lines would not be detected automatically and would impact user experience. One of the best way to improve long-term evolution of the code base would be to increase the coverage. You can go to https://codecov.io/gh/ankidroid/Anki-Android/tree/main/AnkiDroid/src/main/java/com/ichi2 and find functions and part of codes which are not yet tested, and write test for them.

Not 100% of the code have to be covered. In particular, it is useless to cover part of the code that should be rewritten, or ported to rust. Use your own judgement or discuss with maintainers if you have got a doubt.

Coverage can also be generated on your computer. This is mostly useful if you want to check that you correctly covered the code you expected to cover. You can do it by running tests with ./gradlew jacocoTestReport and looking at the report on %AnkiDroidRoot%/AnkiDroid/build/reports/jacoco/jacocoUnitTestReport/html/index.html in your browser

Developer options

Starting on 2.16alpha66, there is a settings category called Developer options. It suits some options aimed at developers and advanced users, and therefore may not be translated. Developer options is always shown on debug builds and are hidden on release versions.

To enable developer options on release versions, you should:

  1. Go to Settings > About
  2. Tap AnkiDroid's logo 6 times
  3. Confirm

Other development tools

SQLite browser

A tool like "SQLite Database Browser" is very useful to understand how your "collection.anki2" file is made, and to test SQL queries. To install it on Ubuntu:

sudo apt-add-repository ppa:linuxgndu/sqlitebrowser
sudo apt-get update
sudo apt-get install sqlitebrowser

Binaries for Windows and Mac can be found here.

Dependabot

If you are maintaining the project, you should monitor the dependabot PRs that come in against the "dependency-updates" branch.

The flow for dependency updates is a circle

  • dependabot makes PRs to the dependency-updates branch
  • if they pass CI and look good, a maintainer will merge them to the dependency branch
  • periodically a maintainer will make a PR to the main development branch from the dependency-updates branch (now with a few dependency updates, most likely)
  • if the PR to the main development branch passes, typically it will be squash-merged to main development branch
  • the maintainer will take a local clone of the dependency-updates branch, rebase it to the main development branch
  • the locally up-to-date dependency updates branch will be force-pushed back to the github dependency-updates branch
  • the cycle repeats: dependabot makes PRs to the dependency-updates branch, etc

If dependabot needs configuration changes (to ignore a dependency or a version), the configuration should be done in the .github/dependabot.yaml file, not via any UI interaction. The difference is that the YAML config is reviewable text while the UI modifies some opaque database and thus is not manageable or reviewable.

HTML javascript inspection

Sometimes you need to look at the HTML and javascript generated by AnkiDroid as it displays cards. There are two ways to do this, either by looking at the raw HTML as a file or by watching it live on the device

Enable the HTML/Javascript Debugging setting in the developer options. Note: before 2.16alpha77 this was in Settings - Advanced.

Then:

Via Chrome Development Tools

To look at the HTML & JavaScript live you can use Chrome WebView Remote debugging. This also works on the chromium-based Edge.

  • Turn on AnkiDroid's Developer options
  • Ensure that AnkiDroid's setting: Developer options - HTML/Javascript Debugging is enabled.
  • Enable USB Debugging via the Android System Menu
  • Connect your PC and phone via USB cable and approve the connection to your computer
  • Open AnkiDroid and review the card you want to inspect
  • Using Chrome on the same PC, browse to: chrome://inspect and you should see the WebView on your phone/emulator [for Edge, open:edge://inspect/#devices]
  • Click Inspect for that WebView and you'll get a full Chrome remote debugging console. From this remote console, you can send JavaScript commands to AnkiDroid and edit HTML/CSS.

Via Eruda Console for Mobile Browsers

https://github.com/liriliri/eruda

This can be used inside the AnkiDroid to view console log like Chrome dev tools.

  • Add following lines to Front/Back of card templates
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script>
  • Save the templates and open deck again.
  • At bottom right corner, there will be button to open console log
  • View Demo

Custom Search Engines

Many browsers support a "custom search engine" function. These save time while developing.

Instructions to add a Search Engine to Chrome: https://zapier.com/blog/add-search-engine-to-chrome/

keyword url Description
issue https://github.com/ankidroid/Anki-Android/issues/%s Jump to Issue/PR
commit https://github.com/ankidroid/Anki-Android/commit/%s Jump to commit hash
gc https://github.com/ankidroid/Anki-Android/search?q=%22Originally+reported+on+Google+Code+with+ID+%s%22&type=issues Jump to Google Code Issue ID (rare, somtimes documented in source code)
acra https://couchdb.ankidroid.org/acralyzer/_design/acralyzer/index.html#/reports-browser/user/%s Search for ACRA user UUID
wiki https://github.com/ankidroid/Anki-Android/search?q=%s&type=wikis Search GitHub Wiki for keyword
acrarep https://couchdb.ankidroid.org/acralyzer/_design/acralyzer/index.html#/report-details/%s Go to ACRA report (rare)

Usage:

  • F6/Select Search Bar
  • Type in keyword
  • Tab
  • Type in search term
  • Enter

Checking database modifications

On Ubuntu Linux:

  1. Install sqlite3 as above and meld: sudo apt-get install sqlite3 meld.
  2. Make sure my desktop and android have about the same clock time.
  3. Import the same shared deck to both.
  4. Perform the same review sequence on both at the same time.
  5. Copy the modified decks for comparison.
  6. Run the following command and check that times are not too different, and that are no other differences:
echo .dump | sqlite3 desktop_collection.anki2 > desktop.dump
echo .dump | sqlite3 android_collection.anki2 > android.dump
diff desktop.dump android.dump

To do from time to time

In addition to maintaining the issues tracker, here are a few things that someone should perform once in a while, maybe every few months or so.

Licenses

  1. Check that all files mention the GNU-GPL license.
  2. Add it to those who don't.

Download localized strings

  1. From AnkiDroid's top directory, run ./tools/update-localizations.py.
  2. To build a new package on Crowdin, the script needs the Crowdin API key. Ask on the mailing list and we will send it to you.
  3. Commit and push.

Alternative markets

  1. Check whether the versions are AnkiDroid's latest release. If not, contact the person responsible for this Market.
  2. Look for new alternative markets (especially in non-English languages) and upload there (please update the Wiki then).
⚠️ **GitHub.com Fallback** ⚠️