Loading repository data…
Loading repository data…
Ivy-Apps / repository
An opinionated collection of learnings from my Android experience as a founding engineer in 4 startups and a contractor at Toptal and Reddit.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
A no-nonsense, opinionated collection of hard-earned lessons from 8+ years of Android development—as a founding engineer at four startups and a contractor at Toptal and Reddit.
Android development isn’t just about cranking out code; it’s a branch of software engineering, and like any engineering discipline, it’s grounded in science. Beneath every library, framework, and pattern lies a foundation of mathematics and formal logic.
This guidebook isn’t here to just teach you how to use tools; it’s here to challenge you to think like an engineer. Together, we’ll dissect Android development through the lens of scientific principles—arming you with the knowledge to not just follow best practices but to deeply understand and, when necessary, improve upon them.
[!TIP] Become a better software engineer. Learn algorithms, data structures, architecture and software design from first principles... ivylearn.app ✨
Modern Android Development often feels like an endless ride on the hype train: every year brings flashy new libraries and practices, while last year’s tools are quickly deprecated. It’s easy to get caught up in this cycle, chasing trends without digging into the real engineering beneath the surface.
But here’s the thing: the shiny stuff won’t last. Instead of memorizing the latest APIs (that’ll be obsolete soon anyway), let’s dive deeper. This guidebook focuses on re-discovering and re-inventing the fundamental principles and technologies that power those shiny tools.
Why reinvent the wheel? Because it’s the best way to step into the shoes of library and API creators. Only then can you truly grasp their design choices—their trade-offs, limitations, and, most importantly, their why.
[!TIP]
Mastery is when you can “rebuild it from scratch if left alone in a basement without internet for a week.” Only then will you wield these tools with confidence—and who knows, you might invent something better.
This guidebook won’t just skim the surface of new trends. It’s about going back to first principles, understanding them deeply, and making Android development feel straightforward—even boring. By the end, building complex Android or Kotlin Multiplatform apps should feel trivial, not overwhelming.
[!NOTE]
Before we jump into the exciting parts, we need to establish a shared understanding of the formal language and core concepts that will guide us through this journey.
[!TIP]
To get the most out of this guidebook, make sure you're familiar with some foundational concepts: implication, equivalence, sets, and the basics of formal logic and mathematics. If any of these sound unfamiliar, no worries—just stick around and read through to the end. Best case? You’ll pick up something new. Worst case? You’ll reinforce what you already know and feel even more confident.
*🚧 means that topic is under construction and is not read, yet
An implication is a one-way logical statement that takes the form "if P, then Q", often written as P ⇒ Q (P implies Q). Here:
Think of it this way: "If it rains, then the ground gets wet." In logical terms, this is P ⇒ Q where:
P ⇒ Q is read as P: "It rains" implies that Q: "The ground gets wet".
This means if it's raining, we can be sure that the ground is wet. However, the reverse isn't always true: if the ground is wet, it doesn't guarantee it has rained—it could be wet for other reasons, like a sprinkler system.
So, an implication is one-way: P ⇒ Q does not mean Q ⇒ P. This is a key part of formal logic that helps build precise reasoning.
[!IMPORTANT] Implications are neither good or bad, they are merely facts: from Cause follows Effect. This makes them suitable for discussing technical approaches because compared to Pros/Cons or Benefits/Drawbacks, implications aren't subjective nor biased.
A set is a collection of unique objects (items) that isn't ordered. Symbolically, sets are denoted with uppercase letters and curly braces. For example: A = {1, 2, 3}, M = { "World", "Hello",}.
Set key properties
{1, 2, 3} = {3, 2, 1}{A, B, C} + B = {A, B, C}A property is a set of implications defining characteristic of an object or concept. When an object has a property, specific conclusions follow, allowing us to precisely define its behavior or characteristics.
Property: Being a square.
If a shape is a square, all four sides are equal in length:
This property also implies that each internal angle is 90 degrees:
Additionally, a square remains unchanged if rotated by 90°, 180°, or 270°:
These implications show that knowing a shape is a square allows us to logically derive its specific attributes and symmetry properties.
[!NOTE] From a property we can derive and prove more implications (e.g. being a square implies having equal diagonals). In other words, a property defines a set of implications that are guaranteed to be true but that doesn't stop us from expanding this set with more implications.
Property: Reactivity in Jetpack Compose.
If a variable is a reactive State, any change in its value triggers recomposition in any Composable that reads it:
State ⇒ R: Composable reading this State recomposes on value changeIf multiple Composables read the same reactive State, any change in that State triggers recomposition in each dependent Composable:
State value changes ⇒ DR: All dependent Composables recomposeIf a Composable reads multiple reactive State values, only the parts of the UI that depend on each specific State recompose when that State changes, optimizing rendering performance:
States ⇒ A: Only affected parts of the UI recompose on specific State changeThese implications allow for precise control and prediction of UI behavior in Compose when working with reactive State in Compose.
[!TIP] Properties provide precise and explicit definitions of behavior through implications. Having formal language and mathematical rigor in place, let's us use formal logic to derive more implications and analyze our approaches. This is a step further than arguing whether the solution is KISS (Keep It Simple Stupid) or DRY (Don't Repeat Yourself).
Equivalence means two statements or properties imply each other within a given context, even if they aren’t identical in every aspect. We write this as A ⇔ B, meaning A implies B and B implies A. Equivalence does not mean the two things are the same; rather, it indicates that they share key behaviors or properties within specific boundaries.
Let's consider a familiar scenario to illustrate equivalence:
Context: In a small town without sprinklers, street cleaning, or other sources of water, the only way the sidewalk becomes wet is when it rains.
Properties and Implications:
Property: If it rains, the sidewalk becomes wet:
Property: If the sidewalk is wet, it must have rained:
Equivalence:
Since both P ⇒ Q and Q ⇒ P hold, we establish P ⇔ Q:
This equivalence means that "It rains" and "The sidewalk is wet" share the same implications within this context; they occur together, making each statement true if and only if the other is also true.
[!IMPORTANT] Changing Context:
If we consider a city where sprinklers or street cleaning exist, the sidewalk could become wet without rain. In this context, Q (the sidewalk is wet) no longer implies P (it is raining), and the equivalence no longer holds.
This example shows how equivalence depends on specific properties and context, allowing us to understand when two statements are logically linked such that one being true guarantees the other is also true.
Context: Examining reactivity in Jetpack Compose — how changes in state values trigger recomposition in observing Composables.
Definitions:
Reactivity Property (R): A change in state value triggers recomposition in any Composable observing that state.
Compose State<T> (S): A state holder that triggers recomposition when its value changes.
StateFlow<T> (F): A flow that, when collected via collectAsState(), triggers recomposition on new emissions.
Implications:
S ⇒ R: Changes in State<T> trigger recomposition.
F ⇒ R: Emissions from StateFlow<T> collected via collectAsState() trigger recomposition.
Behavioral Equivalence Statement:
Since both State<T> and StateFlow<T> satisfy the reactivity property R and their behaviors are indistinguishable to observing Composables, they are behaviorally equivalent in this context:
S ≅ F (S is behaviorally equivalent to F) with respect to reactivity in Jetpack Compose.
Code Demonstration:
// StateFlow to Compose State
@Composable
fun <T> StateFlow<T>.toComposeState(): State<T> {
return this.collectAsState()
}
// Compose State to StateFlow
@Composable
fun <T> State<T>.toStateFlow(): StateFlow<T> {
val flow = remember { MutableStateFlow(this.value) }
LaunchedEffect(this.value) {
flow.value = this.value
}
return flow
}
Conclusion:
Behavioral Equivalence: State<T> and StateFlow<T> are behaviorally equivalent in the context of reactivity in Jetpack Compose and we can use them interchangeably.
[!TIP] In Practice
We'll use S ≅ F in C (behavioral equivalence between S and F in context C) a lot because it'll allow us to find equivalent solutions and then optimize for what we need: e.g. simplicity, performance, type-safety.
- Find a set of equivalent solutions
- Rank them by the properties that we care about.
In programming we often work with collections or need to analyze multiple possible cases. To be able to communicate effectively and prove that our programs work we need a formal way to reason about quantities.
∀ (every) states that some property holds for all possible values in the set.
∀(a,b∈{1,2,3}) implies that a, b can be all possible permutations of two numbers picked from {1,2,3}, precisely: