How to favor Smart Recomposition in Jetpack Compose - android-jetpack-compose

The Jetpack compose documentation says it can skip recomposition if all the inputs are stable and haven't changed. The definition of a type being stable goes like this...
A stable type must comply with the following contract:
The result of equals for two instances will forever be the same for the same two instances.
If a public property of the type changes, Composition will be notified.
All public property types are also stable.
I cannot understand this clearly. Can someone explain to me how compose checks if a type is Stable?? I can understand if compose determines a type as stable, then it will check for equality with equals method. But how to say whether a class is stable so that we can understand and favour smart recomposition as much as possible?
I tried playing around and found the following.
This data class Student is not stable, even if I pass the same instance again, its recomposing
data class Student(
var id:Int=1,
var name:String="Anonymous")
However, the following class is treated as stable and favors smart recomposition when we pass the same instance again
This class is stable when all the public parameters are changed to val
data class Student(
val id:Int=1,
val name:String="Anonymous")
So, I can understand that all the public properties should be immutable. But to my confusion, the following class is also treated as Stable and favors smart recomposition. Why?
This MainActivityVM class is considered Stable when passing like
#Composable
fun StudentList(viewModel:MainActivityVM){
...
}
class MainActivityVM : ViewModel() {
val studentNames = mutableStateListOf<Student>()
var textInputDialogState by mutableStateOf(TextInputDialogState())
var publicMutableProperty:String = "This is mutable"
}
So, I could not figure out exactly how compose checks for stable types. Can someone help me to understand this so that I can efficiently support smart recomposition in my code?

I cannot understand this clearly. Can someone explain to me how compose checks if a type is Stable??
These all are stable by default
All primitive value types: Boolean, Int, Long, Float, Char, etc.
Strings
All Function types (lambdas)
Compose compiler looks at the class and the class cannot be inferred as stable if any one of the field is
mutable (it is associated with a var property)
has a non-stable type (like lists, map etc.. all data structures are unstable by default, as they are not immutable but read-only)
Also, you can rely on compiler and let compiler tell you the stability of a class when in doubt.
You can use
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${buildDir}/composeReports"
]
}
}
It will create classes in composeReports, you can check if compose can infer the stability.
stable class YourClass {
stable val value: CornerSize
<runtime stability> = Stable
}
The generated report will look like above in pseudo-code format.
https://github.com/androidx/androidx/blob/androidx-main/compose/compiler/design/compiler-metrics.md#classes--classestxt

Related

Why does an unitialized typed variable in Dart not implement the type's interface?

I started learning Dart and was reading a critique of some of it's design choices here: https://medium.com/#krossovochkin/dart-language-bad-design-choices-6e35987dc693
The last point that is made is about the poor type system and the author cited this code snippet which prints null:
void main() {
String s = null;
if (s is String) {
print("string");
} else if (s is Null) {
print("null");
} else {
print ("none");
}
}
The is keyword was new to me but "The Dart Programming Language" by Gilad pointed out that is checks the interface implemented by an object's class and not the actual class of an object.
However this didn't help me much because I would think that the variable s is an instance of String and therefore implements String, but the evidence is to the contrary.
I get that the class is not required when defining objects/variables in Dart, and thus I started to wonder if putting the class in the definition just serves as sugar and has little functional purpose. But instead the class of an object/variable is completely determined by its value, and since the default value for all variables in Dart is null, then it would make sense that String is not implemented, but Null is. Is this the case? Am I way of base? Maybe someone could help me wrap my head around this.
The reason is that is checks the interface of the current object itself and not the reference to this object. So yes, s can point to a String object but also allowed to point to null which are a instance of Null: https://api.dart.dev/stable/2.7.2/dart-core/Null-class.html
Since Null does not implement the String interface, this will return false (null is String). This is also mentioned in the article.
The problem the article are trying to focus on are more the fact you are allowed to set the String variable to null value but Null does not implement String.
Well, in the future, this problem are going to be fixed with non-nullable types which are in development right now. When this is implemented you can actually define variables where you can be sure the value will never be null.
So I continued my Dart reading and I came to a better understanding, and that is that Dart is truly optionally typed and that means 2 things:
Type are syntactically optional.
Type has no impact on runtime semantics.
Therefore the actual type annotation of a variable in Dart only serves documentation purposes and it cannot be assumed that a type annotation is true. The actual type of a variable is wholly determined by the value stored at this variable, and in this case it is null.
In truth the variable that I defined in my example is not a String variable or an implementer of the String interface. It is just annotated that it may be/should be/most likely is a string.

Is it possible to require generic type arguments on a Dart class?

A common question, specifically since Dart 2, is if it is possible to require some or all generic type arguments on some or all types - for example List<int> instead of List or MyType<Foo> instead of MyType.
It's not always clear what the intention is though - i.e. is this a matter of style (you/your team likes to see the types), to prevent bugs (omitting type arguments seems to cause more bugs for you/your team), or as a matter of contract (your library expects a type argument).
For example, on dart-misc, a user writes:
Basically, if I have this:
abstract class Mixin<T> {}
I don't have to specify the type:
// Works class Cls extends Object with Mixin<int> {} // ...also works
class Cls extends Object with Mixin {}
Is there some way to make the second one not allowed?
Strictly speaking, yes, and no.
If you want to enforce that type arguments are always used in your own projects (instead of relying on type inference or defaults), you can use optional linter rules such as always_specify_types. Do note this rule violates the official Dart style guide's recommendation of AVOID redundant type arguments on generic invocations in many cases.
If you want to enforce that generic type arguments are always used when the default would be confusing - such as List implicitly meaning List<dynamic>, no such lint exists yet - though we plan on adding this as a mode of the analyzer: https://github.com/dart-lang/sdk/issues/33119.
Both of the above recommendations will help yourself, but if you are creating a library for others to use, you might be asking if you can require a type argument to use your class. For example, from above:
abstract class Mixin<T> {}
abstract class Class extends Object with Mixin {}
The first thing you could do is add a default bounds to T:
// If T is omitted/not inferred, it defaults to num, not dynamic.
abstract class Mixin<T extends num> {}
If you want to allow anything but want to make it difficult to use your class/mixin when T is dynamic you could choose a different default bound, for example Object, or even better I recommend void:
In practice, I use void to mean “anything and I don’t care about the elements”
abstract class Mixin<T extends void> {
T value;
}
class Class extends Mixin {}
void main() {
var c = Class();
// Compile-time error: 'oops' isn't defined for the class 'void'.
c.value.oops();
}
(You could also use Object for this purpose)
If this is a class under your control, you could add an assertion that prevents the class from being used in a way you don't support or expect. For example:
class AlwaysSpecifyType<T> {
AlwaysSpecifyType() {
assert(T != dynamic);
}
}
Finally, you could write a custom lint or tool to disallow certain generic type arguments from being omitted, but that is likely the most amount of work, and if any of the previous approaches work for you, I'd strongly recommend those!

Definitionless, private function

I am new to dart, and I am not familiar with this concept. Some patience is appreciated.
I was reading some code, here, when I came across this. (line 14)
static final none = Motility._(0);
Looking at the second half of the assignment, I can see a private function that takes an integer, but after some searching I do not see a definition in the class.
So, my question is what is this mysterious function? I am assuming this is a feature of the language, but I am having trouble looking it up since I have never heard of this concept!
It invokes the constructor
Motility._(this._bitMask);
https://github.com/munificent/hauberk/blob/master/lib/src/engine/stage/tile.dart#L28
It is not that obvious anymore since new became optional, but it is a common pattern to have private constructors. (Identifiers starting with _ are private in Dart)
Motility is basically an enum that is built this way instead of
enum Motility { none, door, fly, swim, walk, doorAndFly, doorAndWalk, flyAndWalk }
because this way custom values can be assigned.

In Dart's Strong-Mode, can I leave off types from function definitions?

For example, I'd like to just be able to write:
class Dog {
final String name;
Dog(this.name);
bark() => 'Woof woof said $name';
}
But have #Dog.bark's type definition be () => String.
This previously wasn't possible in Dart 1.x, but I'm hoping type inference can save the day and avoid having to type trivial functions where the return type is inferable (the same as it does for closures today?)
The language team doesn't currently have any plans to do inference on member return types based on their bodies. There are definitely cases like this where it would be nice, but there are other cases (like recursive methods) where it doesn't work.
With inference, we have to balance a few opposing forces:
Having smart inference that handles lots of different cases to alleviate as much typing pain as we can.
Having some explicit type annotations so that things like API boundaries are well-defined. If you change a method body and that changes the inferred return type, now you've made a potentially breaking change to your API.
Having a simple boundary between code that is inferred and code that is not so that users can easily reason about which parts of their code are type safe and which need more attention.
The case you bring up is right at the intersection of those. Personally, I lean towards not inferring. I like my class APIs to be pretty explicitly typed anyway, since I find it makes them easier to read and maintain.
Keep in mind that there are similar cases where inference does come into play:
Dart will infer the return type of an anonymous function based on its body. That makes things like lambdas passed to map() do what you want.
It will infer the return type of a method override from the method it is overriding. You don't need to annotate the return type in Beagle.bark() here:
class Dog {
String bark() => "Bark!";
}
class Beagle extends Dog {
final String name;
Dog(this.name);
bark() => 'Woof woof said $name';
}

What are the conventions in Dart about class members' encapsulation and type annotations?

I am new to Dart language. So I would like to know more about some conventions that programmers follow while developing in this language.
Should I always encapsulate my class members as I do, for example in Java? Whenever I create property of class, should I make it private and provide getters/setters? Or there are situations when I should leave them public? If so, what are examples of these situations?
In my opinion, type annotations such as String, int, etc. increase readability of code. They serve as a documentation for other developers who are reading/using my code. Programmer should not think value of what type is storing in this variable right now. So again, what are the situations, that require using var keyword when declaring a variable?
Dmitry.
Thank you.
Thanks for checking out Dart!
No need to encapsulate class fields. Dart creates implicit getters and setters for you. If you need to actually compute something for that field, you can then implement the getter or setter manually. Bonus: this doesn't break consumers of your API.
Example:
class Person {
int age;
}
Later, you want to calculate age:
class Person {
DateTime birthdate;
int get age => new DateTime.now().difference(birthdate).inDays ~/ 365;
}
In both cases, you can do this:
print(person.age);
Pretty cool! No change in API, and no defensive getters and setters (just add them when you need them).
You should use type annotations for the "surface area" of your code. For example, use type annotations for method and function signatures. For cases where the variable's type is very obvious, you should consider using var, because it's more terse and readable.
For example:
String doCoolStuff(int bar) {
var clearlyABool = true;
return 'Hello world';
}
Note that the return type and bar parameter are type annotated, but the clearlyABool uses var because we initialize with a bool.
Feel free to use type annotations everywhere, it's programmer choice. Anecdote: the dart2js source code uses type annotations pretty much everywhere.

Resources