Skip to main content

Overview

AnkiDroid Companion needs to track which deck the user is studying. Since decks can be renamed in AnkiDroid, the app implements a robust system for mapping deck names to deck IDs and persisting these references.

Deck Selection

When a user selects a deck, the app stores both the deck name and deck ID. This dual storage approach handles deck renames gracefully.

SharedPreferences Storage

Deck references are stored in Android’s SharedPreferences for persistence across app sessions:
private static final String DECK_REF_DB = "com.ichi2.anki.api.decks";

/**
 * Save a mapping from deckName to getDeckId in the SharedPreferences
 */
public void storeDeckReference(String deckName, long deckId) {
    final SharedPreferences decksDb = mContext.getSharedPreferences(DECK_REF_DB, Context.MODE_PRIVATE);
    decksDb.edit().putLong(deckName, deckId).apply();
}
From AnkiDroidHelper.java:96-99
This method creates a persistent mapping:
  • Key: Original deck name
  • Value: Deck ID (long)
  • Storage: Private SharedPreferences database

Finding Decks by Name

The findDeckIdByName() method implements intelligent deck lookup with rename detection:
/**
 * Try to find the given deck by name, accounting for potential renaming of the deck by the user as follows:
 * If there's a deck with deckName then return it's ID
 * If there's no deck with deckName, but a ref to deckName is stored in SharedPreferences, and that deck exist in
 * AnkiDroid (i.e. it was renamed), then use that deck. Note: this deck will not be found if your app is re-installed
 * If there's no reference to deckName anywhere then return null
 * @param deckName the name of the deck to find
 * @return the did of the deck in Anki
 */
public Long findDeckIdByName(String deckName) {
    SharedPreferences decksDb = mContext.getSharedPreferences(DECK_REF_DB, Context.MODE_PRIVATE);
    // Look for deckName in the deck list
    Long did = getDeckId(deckName);
    if (did != null) {
        // If the deck was found then return it's id
        return did;
    } else {
        // Otherwise try to check if we have a reference to a deck that was renamed and return that
        did = decksDb.getLong(deckName, -1);
        if (did != -1 && mApi.getDeckName(did) != null) {
            return did;
        } else {
            // If the deck really doesn't exist then return null
            return null;
        }
    }
}
From AnkiDroidHelper.java:110-127

Lookup Strategy

The method uses a three-step strategy:
  1. Direct Lookup: Search for a deck with the exact name
  2. ID-based Lookup: If not found, check SharedPreferences for a stored ID and verify that deck still exists
  3. Return Null: If neither approach finds the deck
Important: If the app is reinstalled, the SharedPreferences data is lost. Renamed decks will only be found if they’re renamed back to the original name.

Getting Deck ID by Name

The private getDeckId() method performs the actual lookup in AnkiDroid’s deck list:
/**
 * Get the ID of the deck which matches the name
 * @param deckName Exact name of deck (note: deck names are unique in Anki)
 * @return the ID of the deck that has given name, or null if no deck was found or API error
 */
private Long getDeckId(String deckName) {
    Map<Long, String> deckList = mApi.getDeckList();
    if (deckList != null) {
        for (Map.Entry<Long, String> entry : deckList.entrySet()) {
            if (entry.getValue().equalsIgnoreCase(deckName)) {
                return entry.getKey();
            }
        }
    }
    return null;
}
From AnkiDroidHelper.java:139-149

Case-Insensitive Matching

Note that the comparison uses equalsIgnoreCase(), making deck name matching case-insensitive.

Getting Current Deck Name

To retrieve the name of the currently selected deck:
public String getCurrentDeckName() {
    StoredState state = getStoredState();
    return mApi.getDeckName(state.deckId);
}
From AnkiDroidHelper.java:129-132
This method:
  1. Retrieves the stored state (which includes the deck ID)
  2. Queries AnkiDroid’s API for the current name of that deck
  3. Returns the current name (which may differ from the original if renamed)

State Storage

In addition to deck references, the app stores the current study state including the active card:
private static final String STATE_DB = "com.ichi2.anki.api.state";
private static final String KEY_CURRENT_STATE = "CURRENT_STATE";

public void storeState(long deckId, CardInfo card) {
    final SharedPreferences cardsDb = mContext.getSharedPreferences(STATE_DB, Context.MODE_PRIVATE);
    
    Map<String, Object> message = new HashMap<>();
    message.put("deck_id", deckId);
    message.put("note_id", card.noteID);
    message.put("card_ord", card.cardOrd);
    message.put("start_time", card.cardStartTime);
    JSONObject js = new JSONObject(message);
    
    cardsDb.edit().putString(KEY_CURRENT_STATE, js.toString()).apply();
}
From AnkiDroidHelper.java:151-162

Stored State Contents

The state includes:
  • deck_id: Which deck is being studied
  • note_id: The current note ID
  • card_ord: The card order within the note
  • start_time: When the card was first shown (for time tracking)

Retrieving Stored State

The stored state can be retrieved and parsed back into a StoredState object:
public StoredState getStoredState() {
    final SharedPreferences cardsDb = mContext.getSharedPreferences(STATE_DB, Context.MODE_PRIVATE);
    String message = cardsDb.getString(KEY_CURRENT_STATE, "");
    
    // No state found in local
    if (message == "") {
        return null;
    }
    
    JSONObject json;
    try {
        json = new JSONObject(message);
        StoredState state = new StoredState();
        state.deckId = json.getLong("deck_id");
        state.noteID = json.getLong("note_id");
        state.cardOrd = json.getInt("card_ord");
        state.cardStartTime = json.getLong("start_time");
        return state;
    } catch (JSONException e) {
        e.printStackTrace();
    }
    return null;
}
From AnkiDroidHelper.java:164-187

Why This Architecture?

This dual-storage approach (name + ID) provides several benefits:

Rename Resilience

Decks can be renamed without breaking the app’s reference

Performance

ID-based lookups are faster than name-based searches

Persistence

SharedPreferences survive app restarts

Graceful Degradation

Falls back to name lookup if ID reference is lost

Limitations

If the app is reinstalled, SharedPreferences data is cleared. Renamed decks will not be found unless:
  • They’re renamed back to the original name
  • The user re-selects the deck in the app

Card Review Process

Learn how cards are reviewed

Notification System

Understand notification display

Build docs developers (and LLMs) love