The journey to Smooth Calendar 2.0
--

So, since i seem to be bad at writing here i thought i should document my journey to Smooth Calendar 2.0. Smooth Calendar is my android widget that i wrote over ten years ago, around the time that android 1.6 was release, and i have updated it ever since. To say that both i and android have evolved during this time is a understatement, so to capitalize on all these improvements i have decided to rewrite it from scratch, using Kotlin and with a modern architecture. So i will document this work in a couple of posts, trying to give the reader some insight to the process.

Preferences

A huge part of Smooth Calendar are the preferences, id say that the code for them is roughly 80% of all the code in there. These were made purely in code, in a not that well structured way, over the last ten years. The support for more exotic preferences were lacking and i mostly did what i had to do to support the feature i was writing. In Smooth Calendar 2.0 these will be done with a PreferenceScreen XML backed by some PreferenceFragmentCompat classes.

What i started to do was look at what general requirements i had, where one was the option to support multiple widget instances, each with their own settings. This is done by appending the widget instance id to the settings key, and is used for most but not all settings. So i wanted a way in the XML to differ between the settings that should have the widget instance id appended to the key and the ones that shouldn't. I looked around at extending every preference, but came to the conclusion that it wasn't needed, i am just going to use the isPersistent property, if it is set to false, which is the most common scenario, i will append the widget id before saving the data, if not ill just save it as it is.

p.setOnPreferenceChangeListener { preference, any ->
    var key = preference.key
    if (!preference.isPersistent) {
        // TODO add the widget id to these settings
        key = "widgetid" + key
       }
    when (any) {
        is Boolean -> sharedPreferences.edit().putBoolean(key, any).apply()
        is Float -> sharedPreferences.edit().putFloat(key, any).apply()
        is Int -> sharedPreferences.edit().putInt(key, any).apply()
        is Long -> sharedPreferences.edit().putLong(key, any).apply()
        is String -> sharedPreferences.edit().putString(key, any).apply()
    }
    false
}

I also decided to use convention over configuration by letting each properties title string be named the same as the key, adding _title to the end, and the summery be named as the key with _summery added. This way i don't need to map them in the XML and can just load them while creating the fragment. I also made sure to never overwrite anything that is already there, leaving me the option to not follow the convention should the need arise in the future.

for (p in list) {
    if (p.title == null) {
        p.title = getString(resources.getIdentifier("setting_" + p.key + "_title", "string", packageName))
    }
    when (p) {
        is CheckBoxPreference -> {
            if (p.summaryOn == null) {
                p.summaryOn = getString(resources.getIdentifier("setting_" + p.key + "_on", "string", packageName))
            }
            if (p.summaryOff == null) {
                p.summaryOff = getString(resources.getIdentifier("setting_" + p.key + "_off", "string", packageName))
            }
        }
        is SwitchPreference -> {
            if (p.summaryOn == null) {
                p.summaryOn = getString(resources.getIdentifier("setting_" + p.key + "_on", "string", packageName))
            }
            if (p.summaryOff == null) {
                p.summaryOff = getString(resources.getIdentifier("setting_" + p.key + "_off", "string", packageName))
            }
        }
        else -> {
            if (p.summary == null) {
                p.summary = getString(resources.getIdentifier("setting_" + p.key + "_summary", "string", packageName))
            }
        }
}

--
Carl Cedergren
2020-03-04 09:54

<< Site update Some more progress on Smooth Calendar 2.0 >>