Read through the latest changes you should know about in Flutter/Dart

Staying updated with advancements in Flutter and Dart is a sign of competence and knowledge for any tech expert focusing on these technologies, and a notion that they are always maintaining a competitive edge. In this article, we will explore the recent updates and changes in these powerful frameworks that every senior developer should be aware of. From the introduction of Dart 3, going full null-safety to new – revolutionary, and groundbreaking – language features like records and pattern matching.

We'll also discuss the exciting additions in Flutter 3.10, including the Impeller rendering engine for iOS and the integration of CameraX for the robust camera functionality. Furthermore, we will highlight Flutter's commitment to security by embracing the SLSA Level 1 standard and implementing features to fortify the software supply chain. Join us as we explore these latest updates and empower ourselves as senior developers.

What is new in Dart 3?

Find your next developer

Kom igång

Full null-safety

Dart has always been a language dedicated to performance, portability, and modernity. With the release of Dart 3, it has reached a significant milestone: the establishment of 100% sound null safety. This is an achievement that distinguishes Dart from many other programming languages.

Null safety is a key component of Dart's type system. With this feature, Dart guarantees that if a type is declared non-null, it can never be null. This assurance eliminates a whole class of coding errors, including the infamous null pointer exceptions, thus enhancing code reliability and safety. Beyond improving code safety, Dart's null safety feature also offers optimization benefits. Dart's compilers and runtimes can now make assumptions about nullability, which is impossible without null safety. This leads to the generation of more efficient code, which can run faster and use less memory.

The transition to null safety in Dart did come with a tradeoff. Migrating existing Dart code to adopt null safety could be challenging because the language semantics have changed significantly. The null safety feature enforces strict checks, which might require substantial changes in the existing codebases. However, this temporary hurdle is outweighed by the long-term benefits of fewer bugs, better performance, and increased developer confidence in the code. Although developers had more than two years to adapt to the null safety feature, I’m sure there are legacy codebases out there that still use the obsolete, pre-2.12 version of Dart.

Language feature #1: Records

Traditionally, Dart functions were capable of returning only a single value. When developers needed to return multiple values from a function, they had to resort to packaging these values into data structures like maps or lists or defining new classes to encapsulate the values. However, these workarounds often led to compromises in type safety and introduced extra friction in the development process. The community's feedback underscored the need for a more elegant solution, with the request for multiple return values being one of the most highly rated issues.

The introduction of records in Dart 3 addresses this need effectively. Records in Dart provide a streamlined syntax for structuring data, making it easier to work with multiple return values and other complex data structures.

Here's an example of how you can use records to return multiple values from a function:

(String, int) userInfo(Map<String, dynamic> json) {
  return (
    json['name'] as String,
    json['height'] as int,
  );
}

At first glance, a record may resemble a list literal in Dart, but it uses parentheses instead of brackets. Records are a general feature in Dart, meaning they can be used beyond function return values. They can be stored in variables, used as keys in a map, added to a list, or even nested within other records. Records can have both unnamed and named fields, providing additional flexibility in how you structure your data.

One crucial aspect of Dart's records is that they are value types and do not have an identity. This characteristic can lead to compiler optimizations, such as the complete erasure of the record object in specific scenarios. Records also come with automatically defined equality (==) operator and hashCode functions, which simplifies the process of comparing records and using them in data structures like sets and maps.

If you're a developer coming from a language that already supports records or similar data structures, here are some best practices to keep in mind when using records in Dart:

  1. Use records when you need to group related values without creating a full-fledged class. The Flutter community engages in an ongoing debate concerning the absence of structs, a language feature comparable to Swift. It is widely acknowledged that using classes as a workaround is not the ideal approach. Introducing structs is deemed necessary but uncertain. This point is mentioned to highlight the community's recent deliberations without complicating matters further. This is especially useful for functions that need to return multiple values.
  2. While you can use both unnamed and named fields in a record, consider using designated fields for clarity, especially when the record has more than a couple of fields or when the purpose of the fields isn't evident from the context.
  3. Since records are value types, they can be used as keys in maps or elements in sets without worrying about identity-based comparison issues.
  4. Remember that records come with built-in == operator and hashCode functions. Make use of these features when you need to compare records or use them in data structures like sets and maps.

Language feature #2: Pattern matching

Pattern matching is a powerful programming feature that allows data structures to be dissected into their individual components. It is an efficient way to examine and manipulate data, particularly in complex structures such as records, lists, and maps.

In Dart 3.0, pattern matching has been introduced in several areas:

  1. Basic destructuring: Dart 3.0 lets developers dissect data structures, including records, lists, and maps, into their constituent parts. If you want to ignore certain elements during this process, Dart 3.0 allows you to use the underscore pattern.
  2. Pattern matching with switch statements: Switch statements in Dart 3.0 have been greatly enhanced to support pattern matching. The requirement for a break at the end of each case has been removed, and cases can be combined using logical operators. This makes switch statements more flexible and powerful. An example could be parsing a character code using a switch statement.
  3. Switch expressions: Dart 3.0 introduces the concept of switch expressions, a concise way to compute a value based on different conditions or cases. An instance of this could be a function that uses a switch expression to return a specific description of the current day of the week.
  4. Exhaustiveness checking: Dart 3.0 offers the ability to check if all possible cases in a switch statement have been handled, a feature known as "exhaustiveness" checking. This is particularly useful when dealing with custom data hierarchies, which can be defined using the new sealed keyword at the top of a class hierarchy.

Language feature #3: class modifiers

The Dart programming language offers a variety of class modifiers to control the behavior and usage of classes and mixins within and outside their defining library.

Class modifiers are keywords that precede the declaration of a class or mixin. For instance, the keyword abstract before a class declaration (abstract class) designates it as an abstract class. Dart supports several class modifiers, including abstract, base, final, interface, sealed, and mixin. However, only the base modifier can precede a mixin declaration. These modifiers do not apply to other declarations such as enum, typedef, or extension.

The usage of these modifiers depends on how the class should function and the behaviors it should support. When no modifier is used, the default behavior allows unrestricted construction or subtyping from any library, including creating new class instances, extending a class to create a subtype, implementing a class or mixin’s interface, or mixing in a mixin or mixin class.

  1. Abstract: The abstract modifier defines a class that does not require a full, concrete implementation of its interface. Abstract classes can be instantiated, neither from their own library nor any external library. Often, these classes have abstract methods. One can define a factory constructor to make an abstract class appear instantiable.
  2. Base: The base modifier enforces the inheritance of a class or mixin’s implementation. A base class prohibits implementation outside of its defining library, ensuring that the base class constructor is invoked whenever a subtype instance is created, and subtypes inherit all private members. Any class that implements or extends a base class must be marked as base, final, or sealed.
  3. Interface: The interface modifier is used to define an interface. While external libraries can implement the interface, they cannot extend it. This restriction prevents method overriding in ways that could cause the fragile base class problem.
  4. Abstract Interface: Combining the interface and abstract modifiers creates a pure interface. This pure interface, like a regular interface, can be implemented but not inherited by other libraries. Furthermore, like an abstract class, a pure interface can contain abstract members.
  5. Final: The final modifier is used to seal the type hierarchy, preventing subtyping from classes outside of the current library. This ensures that instance methods are not overwritten in third-party subclasses, and allows safe incremental changes to the API.
  6. Sealed: The sealed modifier is used to create an enumerable set of subtypes, providing an ability to create an exhaustive switch over these subtypes. Sealed classes, which cannot be extended or implemented outside their own library, are implicitly abstract, but their subclasses are not.

These modifiers offer significant flexibility and control over class behavior in Dart, and their usage can be tailored to the specific needs of a program. Similar concepts exist in many other object-oriented programming languages, though the specific implementation and terminology may vary. For instance, languages like C# and Java have similar concepts of abstract classes, interfaces, and final classes.

One general best practice when using class modifiers or similar constructs in other languages is to use them judiciously and with a clear understanding of their implications. For example, marking a class as final or sealed should be done when there is a specific need to restrict inheritance and not as a default approach. Likewise, abstract classes and interfaces should be used when there is a clear need for abstraction and polymorphism in your design, and not simply because they exist as options. Always consider the design of your application and the potential future needs for extensibility and modification when choosing whether and how to use these features.

What’s new in Flutter 3.10?

Impeller: new rendering engine for iOS

Impeller was initially introduced in a preview form in Flutter 3.7, during which it gathered valuable feedback from users. This feedback was instrumental in guiding over 250 improvements committed to Impeller for the 3.10 release. As a result of these enhancements, all iOS apps built with Flutter 3.10 now utilize Impeller by default, providing smoother app performance and reduce stuttering.

Impeller's enhancements since the 3.7 release have chiefly focused on minimizing its memory footprint. This has been achieved by reducing the number of render passes and intermediate render targets it utilizes. For newer iPhone models, the implementation of lossy texture compression has also helped in decreasing the memory footprint without compromising visual quality. These advancements have notably improved the performance of iPad devices as well.

The benefit of these improvements is evident when considering complex screens, such as the "pull quote" screen in the Wondrous app. The memory footprint of such screens has been almost halved due to these enhancements, resulting in a modest reduction in GPU and CPU load. This improvement might not be immediately noticeable in apps like Wondrous, where frames were already rendering under budget, but it could potentially lead to extended battery life.

Although Impeller is designed to meet the rendering needs of most Flutter apps, developers have the option to opt out. If developers decide to do so, they are encouraged to file an issue on GitHub explaining their reasons. It's also worth noting that there might be minor differences in rendering between Skia and Impeller, which could potentially be bugs. Developers are urged to file issues if they come across such discrepancies. In a future release, the legacy Skia renderer for iOS is planned to be removed to decrease Flutter's size.

Impeller's development is still ongoing, with progress being made on a Vulkan backend. Impeller for Android is also under active development, although it still needs to be ready for preview. More details about these developments are expected to be shared soon.

Early support for CameraX on Android

CameraX, an Android Jetpack library, brings in a plethora of functionalities for camera applications across a wide selection of Android camera hardware. It simplifies the process of adding camera functionality to Android apps, saving developers from the complexity of device fragmentation issues. This library is part of Jetpack, a suite of libraries, tools, and guides that help Android developers write high-quality apps easier.

With the new Flutter 3.10 release, preliminary support for CameraX has been added to the Flutter Camera plugin. This means that developers now have an easy way to integrate CameraX functionality into their Flutter applications, opening up a world of possibilities for developing apps with robust and rich camera features.

The incorporation of CameraX support into Flutter is critical because the current official camera plugin has been plagued with a number of issues. It has been reported to be slow in taking photos, and it has also been criticized for using deprecated APIs. With the addition of CameraX support, these issues are expected to be largely mitigated.

The migration to Jetpack libraries like CameraX is not just a step forward for the Flutter Camera plugin but also signifies a broader shift in Flutter's approach to Android development. Jetpack libraries are considered best practices for native Android developers. They are designed to reduce boilerplate code, simplify complex tasks, and provide a consistent structure that leads to more manageable code.

Jetpack libraries follow best practices, handle lifecycle management, and improve the user interface. By integrating them into Flutter, developers get access to these benefits, making the development process more efficient and the resulting apps more robust and user-friendly.

Embracing SLSA Level 1

The Flutter framework continues to enhance its security measures, this time by aligning itself with the Supply Chain Levels for Software Artifacts (SLSA) Level 1 standard. By conforming to this level, Flutter acknowledges the importance of robust security practices and commits to implementing a range of security features that are instrumental in safeguarding the software supply chain.

As part of embracing SLSA Level 1, Flutter has upgraded its build scripts to accommodate automated builds on trusted build platforms. This step is crucial in maintaining the integrity of the build process. By restricting the built environment to trusted platforms and employing scripted automated builds, the risk of artifact tampering is considerably reduced. This initiative marks a significant stride in enhancing supply chain security, as it ensures the authenticity of the artifacts and prevents unauthorized modifications.

Another crucial security feature that Flutter has implemented is the adoption of a multi-party approval system for release workflows. Under this system, the execution of release workflows is gated by the approval of multiple engineers. This collaborative approach introduces an additional layer of scrutiny to the release process, minimizing the risk of unauthorized or malicious changes being introduced.

Moreover, Flutter now ensures that every execution in the release workflow results in the creation of auditable log records. This means that any change made during the release process is traceable, fostering transparency and accountability. Through this enhancement, Flutter ensures that any discrepancies between the source code and the generated artifacts can be effectively tracked and addressed.

The concept of provenance – the origin or source of something – is integral to ensuring the integrity and security of software artifacts. Acknowledging this, Flutter's beta and stable releases are now built with provenance in mind. This practice assures that the framework release artifacts are built from trusted sources and have contents that match the expected ones.

Additionally, Flutter now makes available links that allow users to view and verify the provenance of each release on the SDK archive. This move towards greater transparency allows users to verify the integrity of the release artifacts, ensuring they come from a trusted source and are free from tampering.

Summary

In summary, this article covers the latest updates and changes in Flutter and Dart that are vital for senior developers. Dart 3 marks a significant milestone with the introduction of full null safety, guaranteeing code reliability and performance optimization.

The addition of language features like records and pattern matching further streamlines data manipulation and brings greater flexibility to the development process.

Flutter 3.10 introduces the Impeller rendering engine, enhancing app performance and reducing stuttering on iOS devices. The integration of CameraX provides developers with an easier and more efficient way to incorporate advanced camera features into their Flutter applications. Moreover, Flutter's focus on security is evident through its alignment with the SLSA Level 1 standard, ensuring a robust and secure software supply chain.

By staying up-to-date with these updates, senior developers can leverage the latest advancements and create high-quality, performant applications with Flutter and Dart.

Hitta din nästa utvecklare inom ett par dagar

Ge oss 25 minuter av din tid, så kommer vi att:

  • Sätta oss in i dina utmaningar och behov
  • Berätta om våra seniora och beprövade utvecklare
  • Förklara hur vi kan matcha dig med precis rätt utvecklare

Låt oss ta ett kort digitalt möte.