-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Getting started
This page lists details on how to get started with developing Lawnchair.
This page assumes familiarity with Kotlin, Jetpack Compose, and the basics of Android development. We also assume that you've read the Lawnchair contributing guide.
Tip
Lawnchair is a complex project, containing code both from AOSP and several compatibility layers. It is normal to be confused at first - make sure to take the code step-by-step, making smaller contributions first. Don't try to change all of Launcher3 views to Jetpack Compose as your first contribution!
- Workspace - represents the homescreen
- Hotseat - represents the dock
- Allapps - represents the app drawer
- Smartspace - represents At a Glance. The smartspace view actually is one of the QSB views (it acts as the first item speanning the whole grid)
- Quickstep - represents the recents view
- QSB - quick search bar. There are three of them: one in the workspace, one in the hotseat, and one in the allapps view.
- DT2S - double tap to sleep
- Device Profile - important aspects of the device, like size and orientation
- IDP - invariant device profile - refers to the the device profile that does not need activity access. This forms the basis of the launcher grid and UI
- Create a new class in
lawnchair/app/lawnchair/qsb/providers/
. Name itSearchEngineName
. - Make the class inherit from QsbSearchProvider. Visit the other providers for additional examples. For a website, a general template is as follows:
data object Engine : QsbSearchProvider(
id = "engine",
name = R.string.search_provider_engine,
icon = R.drawable.ic_engine,
packageName = "",
website = "https://engine-url.com/",
type = QsbProviderType.WEBSITE,
)
- Add the vector drawables and the missing strings in the
lawnchair/res/
folder. - Add the engine name in
QsbSearchProvider
, sorted alphabetically:
sealed class QsbSearchProvider(...) {
// ...
companion object {
// ...
fun values() = listOf(
AppSearch,
EngineName,
// other engines go here
)
// ...
- Done! You're ready to open a PR.
Lawnchair uses the view system and Jetpack Compose (Compose) for most of its UI.
Views are used within:
- All of the non-modified parts of Launcher3's UI
- Search (both in the QSB and the search result UI)
- Smartspace
Compose is used within:
- Lawnchair settings
- Custom bottom sheet pop-ups
Lawnchair's preferences are handled via the following:
-
PreferenceManager
(prefs
), using SharedPreferences as the backend -
PreferenceManager2
(prefs2
), using Preference Datastore as the backend
You should prefer prefs2
over prefs
for newer settings, as the former provides the ability to use non-primitive data types and getting the default value from config.xml
.
First, make a new key. We will use example_pref
as the key, with a Boolean
type defaulting to false
. Depending on whether you use prefs
or prefs2
, the code may differ slightly. See the below code block for more info.
// PreferenceManager.kt
class PreferenceManager : ... {
// ...
val examplePref = BoolPref("example_pref", false)
}
// PreferenceManager2.kt
class PreferenceManger2 ... {
// ...
val examplePref = preference(
key = booleanPreferencesKey(name = "example_pref"),
defaultValue = false,
)
}
The preference can be used by getting either the prefs
or prefs2
instance:
val prefs = PreferenceManager.getInstance(context)
val prefs2 = PreferenceManager2.getInstance(context)
To actually use the preference, you need to get the preference key value first:
// For prefs
val key: Boolean = prefs.examplePref.get()
// For prefs2
val key: Boolean = prefs2.examplePref.firstBlocking()
Setting the preference value shouldn't really happen within a launcher UI, but rather in the Preferences UI. See the next section to view info about that.
The .getAdapter()
system, used both by prefs
and prefs2
, allows for uniform state handling of preferences in the Lawnchair settings UI.
See the below code block for explanations.
@Composable
fun BasicExample() {
val prefs = preferenceManager()
val prefs2 = preferenceManager2()
val key = prefs.examplePref.getAdapter()
// or, for prefs2:
val key = prefs2.examplePref.getAdapter()
// SwitchPreference allows you to handle Boolean preferences
SwitchPreference(
adapter = key,
label = "Example pref",
)
}
@Composable
fun BasicExampleWithCustomComponent() {
val prefs2 = preferenceManager2()
val key = prefs2.examplePref.getAdapter()
val value = key.state.value // get the boolean value as Compose state
Column {
Text("State: $value")
Button(
onClick = {
// changes the value to a new value (for here, the inverse of the boolean) and updates the compose state
key.onChange(value = !it)
}
) {
Text("Toggle state")
}
}
}