Japanese Dictionary for Android

I use an iPhone and am frustrated that the major free and very good offline Japanese dictionary for German speakers in their AppStore (Imiwa) has so many features it uses up almost 1GB. That conflicts with my family picture hoarding habits, so I felt like making an offline dictionary for iPhone that uses only the bare minimum of storage for myself. But realistically that thing will probably never really monetize so I decided to do it for Android instead because all Google wants is a one-off 25USD payment and you can use any machine you like for developing.

I should briefly mention that installing Android Studio is a lengthy process. And that if you are interested in doing plain JAVA projects in parallel or for preparing your mobile stuff, IntelliJ (Android Studio's mother so to speak) can do both kinds of projects quite conveniently with the same luxurious autocomplete. Either way a physical device for testing is nice if your computer's performance is too poor for the emulator to run properly.

They say SQLite is quite common for mobile applications and I like that you don't have a server process in the background. The functionality it misses (outer limits etc.) is not really important for the dictionary app. Back to the large JMDict XML-file: to turn that into SQL I search/replaced XML-tags to flags and delimiters that say whether the next text is a new id number, or a word in what language etc. The resulting text file was for another JAVA console app to loop through, recognize flags and store the following strings, each flag with its corresponding prepared Insert-statement to execute with the following string as its parameter. That might at first not sound too bad but really it is - probably most actual database people learn at least one entirely new language solely to avoid such a scenario - I did not and instead simply pressed run before going to bed and the database was ready in the next morning. But obviously 6h+ for roughly 30mb worth of database is unacceptable for industrial purposes. That database file is not sorted and too big for full table searches. Binary searches are enabled through indexing, so we can do what the multimaps do. Running the index statement is very fast, but it inflates the storage you need. Funny how in the end the c++ multimap and sqlite file sizes end up roughly the same.

By the way, to deploy small databases people usually ship their app with a text file and setup code to build a new database file on the customer's device when they first run the app after install. That easy way of course was not an option here, can't freeze their phones for, say, 12 hours or so. And instead of messing with SQLiteAssetHelper for hours and hours by myself to find out how to deploy the ready .db file (I guess they want you to put big databases on online servers and not on each individual phone) I used this. Thank you, Jeff Gilfelt.

Setting up the single-threaded interface in the XML system was kind of easy, but it turns out that anything but equality searches take way too much time. They freeze the display for too long. There is more than one way to handle that, I ended up with callback interfaces for AsyncTasks. So here is what happens in my code (not going to upload the whole thing it has too many last minute quirks for text format): say you enter text and touch the magnifying glass (SearchButton). onclicklistener executes an AsyncTask BackgroundAcquireKeyList, to get BackKeyList, the list of dictionary ID numbers for that word. The MainActivity thread has an implementation of onKeyListAcquired, an interface with the function onKeyListDownloaded(), I shouldn't have called that download. BackgroundAcquireKeyList in turn is constructed with "callback" of onKeyListAcquired, like that:
public class BackgroundAcquireKeyList extends AsyncTask {
public onKeyListAcquired callback;
   public BackgroundAcquireKeyList(onKeyListAcquired callback){
which is a callback parameter (is that the right word?) that calls onKeyListDownloaded() in BackgroundAcquireKeyList's onPostExecute() method, right after updating BackKeyList to what we want.
   protected void onPostExecute(ArrayList keyList) {
     BackKeyList = keyList;
And MainActivity's implementation/override of onKeyListDownloaded - that's where the separate threads finally talk to each other - gets that BackKeyList and immediately passes it into a function I factored out and called getGoing() (not very meaningful, sorry) because it's a bit longer.

Now to the second AsyncTask and callback interface: for each item in BackKeyList getGoing() starts a new BackgoundAcquireListItem AsyncTask that will query the text corresponding to that key and the selected languages from the database. MainActivity also implements - same pattern - onListItemAcquired, an interface with the method onListItemDownloaded() and BackgoundAcquireListItem is constructed with "this.callback=callback" of onListItemAcquired, who will call onListItemDownloaded - as implemented/overridden in MainActivity - with an updated resultitem in its onPostExecute() method, such that the result item will be added to the listview on display (straight away by onListItemDownloaded). By the way AsyncTask creates something like a queue of threads where one id number is processed after another. So no matter how large the BackKeyList is, BackgoundAcquireListItem will not escalate into a death spiral where simultaneously too many threads are fired at the same time, such that the overhead cost of switching between threads is so large the whole program stalls.

Ideally I would have liked to start processing ID numbers to new list items even before the whole BackKeyList is ready, but I don't know how to get slices of a result out of a running SQL query. That would be great because the first select statement takes so much more time than the reverse lookup of individual keys. But at least in case the phone's processor is really really slow, the results will pop up slowly one after another so you can start reading the first one before the 100th has even been queried. And of course the display is reactive all the time, albeit just for show. Of course I made a running query (both stages) block any new ones - you don't want the results of different queries radomly mix in the same list view.

App Screenshot
Go to store  

Copyright © 2016-2024