Dive into Dart's patterns and records
1. Introduction
Dart 3 introducespatterns to the language, a major new category of grammar. Beyond this new way to write Dart code, there are several other language enhancements, including
- records for bundling data of different types,
- class modifiers for controlling access, and
- newswitch expressions andif-case statements.
These features expand the choices you have when writing Dart code. In this codelab, you learn how to use them to make your code more compact, streamlined, and flexible.
This codelab assumes you have some familiarity with Flutter and Dart. If you feel a little rusty, consider brushing up on the basics with the following resources:
What you'll build
This codelab creates an application that displays a JSON document in Flutter. The application simulates JSON coming from an external source. The JSON contains document data such as the modification date, title, headers, and paragraphs. You write code to neatly pack data into records so that it can be transferred and unpacked wherever your Flutter widgets need it.
You then use patterns to build the appropriate widget when the value matches that pattern. You also see how to use patterns to destructure data into local variables.

What you'll learn
- How to create a record that stores multiple values with different types.
- How to return multiple values from a function using a record.
- How to use patterns to match, validate, and destructure data from records and other objects.
- How to bind pattern-matched values to new or existing variables.
- How to use new switch statement capabilities, switch expressions, and if-case statements.
- How to take advantage ofexhaustiveness checking to ensure that every case is handled in a switch statement or switch expression.
2. Set up your environment
- Install theFlutter SDK.
- Set up an editor such as Visual Studio Code (VS Code).
- Go through thePlatform setup steps for at least one target platform (iOS, Android, Desktop, or a web browser).
3. Create the project
Before diving into patterns, records, and other new features, take a moment to create a Flutter project for which you write all your code.
Create a Flutter project
- Use the
flutter createcommand to create a new project namedpatterns_codelab. The--emptyflag prevents the creation of the standard counter app in thelib/main.dartfile, which you'd have to remove anyway.
flutter create --empty patterns_codelab
- Then, open the
patterns_codelabdirectory using VS Code.
code patterns_codelab

Set the minimum SDK version
- Set the SDK version constraint for your project to depend on Dart 3 or above.
pubspec.yaml
environment:sdk:^3.0.0Did you know? This is your first introduction to Dart 3! The caret^ syntax can now be used for SDK constraints (although it has been supported for packages since Dart 1.8). Previously, this would be written>=3.0.0<4.0.0.
4. Set up the project
In this step, you create or update two Dart files:
- The
main.dartfile that contains widgets for the app, and - The
data.dartfile that provides the app's data.
You will continue modifying both of these files in the subsequent steps.
Define the data for the app
- Create a new file,
lib/data.dart, and add the following code to it:
lib/data.dart
import'dart:convert';classDocument{finalMap<String,Object?>_json;Document():_json=jsonDecode(documentJson);}constdocumentJson='''{ "metadata": { "title": "My Document", "modified": "2023-05-10" }, "blocks": [ { "type": "h1", "text": "Chapter 1" }, { "type": "p", "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit." }, { "type": "checkbox", "checked": false, "text": "Learn Dart 3" } ]}''';Imagine a program receiving data from an external source, like an I/O stream or HTTP request. In this codelab, you simplify that more-realistic use case by mocking incoming JSON data with a multi-line string in thedocumentJson variable.
The JSON data is defined in theDocument class. Later in this codelab, you add functions that return data from the parsed JSON. This class defines and initializes the_json field in its constructor.
Did you know? You can pressCommand orControl and click functions, classes, and libraries in VS Code to see where they are defined.
Try doing this onjsonDecode and see the editor open to the declaration in thedart:convert library.
Run the app
Theflutter create command creates thelib/main.dart file as part of the default Flutter file structure.
- To create a starting point for the application, replace the contents of
main.dartwith the following code:
lib/main.dart
import'package:flutter/material.dart';import'data.dart';voidmain(){runApp(constDocumentApp());}classDocumentAppextendsStatelessWidget{constDocumentApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(theme:ThemeData(),home:DocumentScreen(document:Document()),);}}classDocumentScreenextendsStatelessWidget{finalDocumentdocument;constDocumentScreen({requiredthis.document,super.key});@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('Title goes here')),body:constColumn(children:[Center(child:Text('Body goes here'))]),);}}You added the following two widgets to the app:
DocumentAppsets up the latest version ofMaterial Design for theming the UI.DocumentScreenprovides the visual layout of the page using theScaffoldwidget.
Tip: Splitting your UI into separate widgets reduces the size of yourbuild() methods.
- To make sure everything is running smoothly, run the app on your host machine by clickingRun and Debug:

- By default, Flutter chooses whichever target platform is available. To change the target platform, select the current platform on the Status Bar:

You should see an empty frame with thetitle andbody elements defined in theDocumentScreen widget:

5. Create and return records
In this step, you use records to return multiple values from a function call. Then, you call that function in theDocumentScreen widget to access the values and reflect them in the UI.
Create and return a record
- In
data.dart, add a new getter method to the Document class calledmetadatathat returns a record:
lib/data.dart
import'dart:convert';classDocument{finalMap<String,Object?>_json;Document():_json=jsonDecode(documentJson);(String,{DateTimemodified})getmetadata{//Addfromhere...consttitle='My Document';finalnow=DateTime.now();return(title,modified:now);}//tohere.}The return type for this function is a record with two fields, one with the typeString, and the other with the typeDateTime.
The return statement constructs a new record by enclosing the two values in parenthesis,(title, modified: now).
The first field is positional and unnamed, and the second field is namedmodified.
Summary:
- Records are comma-delimited field lists enclosed in parentheses.
- Record fields can each have a different type, so records can collect multiple types.
- Records can contain both named and positional fields, like argument lists in a function.
- Records can be returned from a function, so they enable you to return multiple values from a function call.
Access record fields
- In the
DocumentScreenwidget, call themetadatagetter method in thebuildmethod so that you can get your record and access its values:
lib/main.dart
classDocumentScreenextendsStatelessWidget{finalDocumentdocument;constDocumentScreen({requiredthis.document,super.key});@overrideWidgetbuild(BuildContextcontext){finalmetadataRecord=document.metadata;//Addthisline.returnScaffold(appBar:AppBar(title:Text(metadataRecord.$1)),//Modifythisline,body:Column(children:[//Andthefollowingline.Center(child:Text('Last modified ${metadataRecord.modified}')),],),);}}Themetadata getter method returns a record, which is assigned to the local variablemetadataRecord. Records are a light and easy way to return multiple values from a single function call and assign them to a variable.
To access the individual fields composed in that record, you can use records built-in getter syntax.
- To get a positional field (a field without a name, like
title), use the getteron the record. This returns only unnamed fields. - Named fields like
modifieddon't have a positional getter, so you can use its name directly, likemetadataRecord.modified.
To determine the name of a getter for a positional field, start at$1 and skip named fields. For example:
varrecord=(named:'v','y',named2:'x','z');print(record.$1);//printsyprint(record.$2);//printsz- Hot reload to see the JSON values displayed in the app. The VS Code Dart plugin hot-reloads every time you save a file.

You can see that each field did, in fact, maintain its type.
- The
Text()method takes a String as its first argument. - The
modifiedfield is a DateTime, and is converted into aStringusingstring interpolation.
The other type-safe way to return different types of data is to define a class, which is more verbose.
6. Match and destructure with patterns
Records can efficiently collect different types of data and easily pass it around. Now, improve your code usingpatterns.
A pattern represents a structure that one or more values can take, like a blueprint. Patterns compare against actual values to determine if theymatch.
Some patterns, when they match,destructure the matched value by pulling data out of it. Destructuring lets you unpack values from an object to assign them to local variables, or perform further matching on them.
Destructure a record into local variables
- Refactor the
buildmethod ofDocumentScreento callmetadataand use it to initialize apattern variable declaration:
lib/main.dart
classDocumentScreenextendsStatelessWidget{finalDocumentdocument;constDocumentScreen({requiredthis.document,super.key});@overrideWidgetbuild(BuildContextcontext){final(title,modified:modified)=document.metadata;//ModifyreturnScaffold(appBar:AppBar(title:Text(title)),//Modifyfromhere...body:Column(children:[Center(child:Text('Last modified $modified'))]),);//Tohere.}}The record pattern(title, modified: modified) contains twovariable patterns that match against the fields of the record returned bymetadata.
- The expression matches the subpattern because the result is a record with two fields, one of which is named
modified. - Because they match, the variable declaration pattern destructures the expression, accessing its values and binding them to new local variables of the same types and names,
String titleandDateTime modified.
There is a shorthand for when the name of a field and the variable populating it are the same. Refactor thebuild method ofDocumentScreen as follows.
lib/main.dart
classDocumentScreenextendsStatelessWidget{finalDocumentdocument;constDocumentScreen({requiredthis.document,super.key});@overrideWidgetbuild(BuildContextcontext){final(title,:modified)=document.metadata;//ModifyreturnScaffold(appBar:AppBar(title:Text(title)),body:Column(children:[Center(child:Text('Last modified $modified'))]),);}}The syntax of the variable pattern:modified is shorthand formodified: modified. If you want a new local variable of a different name, you can writemodified: localModified instead.
- Hot reload to see the same result as in the previous step. The behavior is exactly the same; you just made your code more concise.
7. Use patterns to extract data
In certain contexts, patterns don't only match and destructure but can also make adecision aboutwhat the code does, based on whether or not the pattern matches. These are calledrefutable patterns.
The variable declaration pattern you used in the last step is anirrefutable pattern: the value must match the pattern or it's an error and destructuring won't happen. Think of any variable declaration or assignment; you can't assign a value to a variable if they're not the same type.
Refutable patterns, on the other hand, are used in control flow contexts:
- Theyexpect that some values they compare against will not match.
- They are meant toinfluence the control flow, based on whether or not the value matches.
- Theydon't interrupt execution with an error if they don't match, they just move to the next statement.
- They can destructure and bind variables that areonly usable when they match
Read JSON values without patterns
In this section, you read data without pattern matching to see how patterns can help you work with JSON data.
- Replace the previous version of
metadatawith one that reads values from the_jsonmap. Copy and paste this version ofmetadatainto theDocumentclass:
lib/data.dart
classDocument{finalMap<String,Object?>_json;Document():_json=jsonDecode(documentJson);(String,{DateTimemodified})getmetadata{if(_json.containsKey('metadata')){//Modifyfromhere...finalmetadataJson=_json['metadata'];if(metadataJsonisMap){finaltitle=metadataJson['title']asString;finallocalModified=DateTime.parse(metadataJson['modified']asString,);return(title,modified:localModified);}}throwconstFormatException('Unexpected JSON');//tohere.}}This code validates that the data is structured correctly without using patterns. In a later step, you use pattern matching to perform the same validation using less code. It performs three checks before doing anything else:
- The JSON contains the datastructure you expect:
if (_json.containsKey('metadata')) - The data has thetype you expect:
if (metadataJson is Map) - That the data isnot null, which is implicitly confirmed in the previous check.
Read JSON values using a map pattern
With a refutable pattern, you can verify that the JSON has the expected structure using amap pattern.
- Replace the previous version of
metadatawith this code:
lib/data.dart
classDocument{finalMap<String,Object?>_json;Document():_json=jsonDecode(documentJson);(String,{DateTimemodified})getmetadata{if(_jsoncase{//Modifyfromhere...'metadata':{'title':Stringtitle,'modified':StringlocalModified},}){return(title,modified:DateTime.parse(localModified));}else{throwconstFormatException('Unexpected JSON');}//tohere.}}Here, you see a new kind of if-statement (introduced in Dart 3), theif-case. The case body only executes if the case pattern matches the data in_json. This match accomplishes the same checks you wrote in the first version ofmetadata to validate the incoming JSON. This code validates the following:
_jsonis a Map type._jsoncontains ametadatakey._jsonis not null._json['metadata']is also a Map type._json['metadata']contains the keystitleandmodified.titleandlocalModifiedare strings and aren't null.
If the value doesn't match, thepattern refutes (refuses to continue execution) and proceeds to theelse clause. If the match is successful, the pattern destructures the values oftitle andmodified from the map and binds them to new local variables.
For a full list of patterns, see thetable in the Patterns section of the feature specification.
Summary: Using patterns in this step tests types, destructures, and binds values using a single statement.
8. Prepare the app for more patterns
So far, you address themetadata part of the JSON data. In this step, you refine your business logic a bit more in order to handle the data in theblocks list and render it into your app.
{"metadata":{// ...},"blocks":[{"type":"h1","text":"Chapter 1"},// ...]}Create a class that stores data
- Add a new class,
Block, todata.dart, which is used to read and store the data for one of the blocks in the JSON data.
lib/data.dart
classBlock{finalStringtype;finalStringtext;Block(this.type,this.text);factoryBlock.fromJson(Map<String,dynamic>json){if(jsoncase{'type':finaltype,'text':finaltext}){returnBlock(type,text);}else{throwconstFormatException('Unexpected JSON format');}}}The factory constructorfromJson() uses the same if-case with a map pattern that you've seen before.
You'll see that the JSON data looks like the expected pattern, even though it has an extra piece of information calledchecked that isn't in the pattern. This is because when you're using these kinds of patterns (called "map patterns"), they only care about the specific things you've defined in the pattern and ignore anything else in the data.
Return a list of Block objects
- Next, add a new function,
getBlocks(), to theDocumentclass.getBlocks()parses the JSON into instances of theBlockclass and returns a list of blocks to render in your UI:
lib/data.dart
classDocument{finalMap<String,Object?>_json;Document():_json=jsonDecode(documentJson);(String,{DateTimemodified})getmetadata{if(_jsoncase{'metadata':{'title':Stringtitle,'modified':StringlocalModified},}){return(title,modified:DateTime.parse(localModified));}else{throwconstFormatException('Unexpected JSON');}}List<Block>getBlocks(){//Addfromhere...if(_jsoncase{'blocks':ListblocksJson}){return[for(finalblockJsoninblocksJson)Block.fromJson(blockJson)];}else{throwconstFormatException('Unexpected JSON format');}}//tohere.}ThegetBlocks() function returns a list ofBlock objects, which you use later to build the UI. A familiar if-case statement performs validation and casts the value of theblocks metadata into a newList namedblocksJson (without patterns, you'd need thetoList() method to cast).
The list literal contains acollection for in order to fill the new list withBlock objects.
This section doesn't introduce any pattern-related features you haven't already tried in this codelab. In the next step, you prepare to render the list items in your UI.
9. Use patterns to display the document
You now successfully destructure and recompose your JSON data, using an if-case statement and refutable patterns. But if-case is only one of the enhancements to control flow structures that come with patterns. Now, you apply your knowledge of refutable patterns to switch statements.
Control what's rendered using patterns with switch statements
- In
main.dart, create a new widget,BlockWidget, that determines the styling of each block based on itstypefield.
lib/main.dart
classBlockWidgetextendsStatelessWidget{finalBlockblock;constBlockWidget({requiredthis.block,super.key});@overrideWidgetbuild(BuildContextcontext){TextStyle?textStyle;switch(block.type){case'h1':textStyle=Theme.of(context).textTheme.displayMedium;case'p'||'checkbox':textStyle=Theme.of(context).textTheme.bodyMedium;case_:textStyle=Theme.of(context).textTheme.bodySmall;}returnContainer(margin:constEdgeInsets.all(8),child:Text(block.text,style:textStyle),);}}The switch statement in thebuild method switches on thetype field of theblock object.
- The first case statement uses aconstant string pattern. The pattern matches if
block.typeis equal to the constant valueh1. - The second case statement uses alogical-or pattern with two constant string patterns as its subpatterns. The pattern matches if
block.typematches either of the subpatternsporcheckbox.
Did you know?logical-and patterns (&&) can also be used.
- The final case is awildcard pattern,
_. Wildcards in switch cases match everything else. They behave the same asdefaultclauses, which are still allowed in switch statements (they're just a little more verbose).
Wildcard patterns can be used wherever a pattern is allowed—for example, in a variable declaration pattern:var (title, _) = document.metadata;
In this context, the wildcard doesn't bind any variable. It discards the second field.
Note: Switch statements no longer requirebreak as of Dart 3. Non-empty cases jump to the end of the statement when they reach the end of their body.
In the next section, you learn about more switch features after displaying theBlock objects.
Display the document content
Create a local variable that contains the list ofBlock objects by callinggetBlocks() in theDocumentScreen widget'sbuild method.
- Replace the existing
buildmethod inDocumentationScreenwith this version:
lib/main.dart
classDocumentScreenextendsStatelessWidget{finalDocumentdocument;constDocumentScreen({requiredthis.document,super.key});@overrideWidgetbuild(BuildContextcontext){final(title,:modified)=document.metadata;finalblocks=document.getBlocks();//AddthislinereturnScaffold(appBar:AppBar(title:Text(title)),body:Column(children:[Text('Last modified: $modified'),//ModifyfromhereExpanded(child:ListView.builder(itemCount:blocks.length,itemBuilder:(context,index){returnBlockWidget(block:blocks[index]);},),),//tohere.],),);}}The lineBlockWidget(block: blocks[index]) constructs aBlockWidget widget for each item in the list of blocks returned from thegetBlocks() method.
- Run the application, and then you should see the blocks appearing on screen:

10. Use switch expressions
Patterns add a lot of capabilities toswitch andcase. To make them usable in more places, Dart hasswitch expressions. A series of cases can provide a value directly to a variable assignment or return statement.
Convert the switch statement into a switch expression
The Dart analyzer providesassists to help you make changes to your code.
- Move your cursor to the switch statement from the previous section.
- Click on the lightbulb to view the available assists.
- Select theConvert to switch expression assist.

The new version of this code looks like this:
lib/main.dart
classBlockWidgetextendsStatelessWidget{finalBlockblock;constBlockWidget({requiredthis.block,super.key});@overrideWidgetbuild(BuildContextcontext){TextStyle?textStyle;//ModifyfromheretextStyle=switch(block.type){'h1'=>Theme.of(context).textTheme.displayMedium,'p'||'checkbox'=>Theme.of(context).textTheme.bodyMedium,_=>Theme.of(context).textTheme.bodySmall,};//tohere.returnContainer(margin:constEdgeInsets.all(8),child:Text(block.text,style:textStyle),);}}A switch expression looks similar to a switch statement, but it eliminates thecase keyword and uses=> to separate the pattern from the case body. Unlike switch statements, switch expressions return a value and can be used anywhere an expression can be used.
11. Use object patterns
Dart is an object-oriented language, so patterns apply to all objects. In this step, you switch on anobject pattern and destructure object properties to enhance the date rendering logic of your UI.
Note: There's no migration necessary to support patterns; you can immediately start using pattern matching on your existing Dart classes.
Extract properties from object patterns
In this section, you improve how the last modified date is displayed using patterns.
- Add the
formatDatemethod tomain.dart:
lib/main.dart
StringformatDate(DateTimedateTime){finaltoday=DateTime.now();finaldifference=dateTime.difference(today);returnswitch(difference){Duration(inDays:0)=>'today',Duration(inDays:1)=>'tomorrow',Duration(inDays:-1)=>'yesterday',Duration(inDays:finaldays,isNegative:true)=>'${days.abs()}daysago',Duration(inDays:finaldays)=>'$daysdaysfromnow',};}This method returns a switch expression that switches on the valuedifference, aDuration object. It represents the span of time betweentoday and themodified value from the JSON data.
Each case of the switch expression is using an object pattern that matches by calling getters on the object's propertiesinDays andisNegative. The syntax looks like it might be constructing a Duration object, but it's actually accessing fields on thedifference object.
The first three cases use constant subpatterns0,1, and-1 to match the object propertyinDays and return the corresponding string.
The last two cases handle durations beyond today, yesterday, and tomorrow:
- If the
isNegativeproperty matches theboolean constant patterntrue, meaning the modification date was in the past, it displaysdays ago. - If that case doesn't catch the difference, then duration must be a positive number of days (no need to explicitly verify with
isNegative: false), so the modification date is in the future and displaysdays from now.
Add formatting logic for weeks
- Add two new cases to your formatting function in order to identify durations longer than 7 days so that the UI can display them asweeks:
lib/main.dart
StringformatDate(DateTimedateTime){finaltoday=DateTime.now();finaldifference=dateTime.difference(today);returnswitch(difference){Duration(inDays:0)=>'today',Duration(inDays:1)=>'tomorrow',Duration(inDays:-1)=>'yesterday',Duration(inDays:finaldays)whendays >7=>'${days~/7}weeksfromnow',//AddfromhereDuration(inDays:finaldays)whendays <-7=>'${days.abs()~/7}weeksago',//tohere.Duration(inDays:finaldays,isNegative:true)=>'${days.abs()}daysago',Duration(inDays:finaldays)=>'$daysdaysfromnow',};}This code introducesguard clauses:
- A guard clause uses the
whenkeyword after a case pattern. - They can be used in if-cases, switch statements, and switch expressions.
- They only add a condition to a patternafter it's matched.
- If the guard clause evaluates to false, the entire pattern isrefuted, and execution proceeds to the next case.
Add the newly formatted date to the UI
- Finally, update the
buildmethod inDocumentScreento use theformatDatefunction:
lib/main.dart
classDocumentScreenextendsStatelessWidget{finalDocumentdocument;constDocumentScreen({requiredthis.document,super.key});@overrideWidgetbuild(BuildContextcontext){final(title,:modified)=document.metadata;finalformattedModifiedDate=formatDate(modified);//Addthislinefinalblocks=document.getBlocks();returnScaffold(appBar:AppBar(title:Text(title)),body:Column(children:[Text('Last modified: $formattedModifiedDate'),//ModifythislineExpanded(child:ListView.builder(itemCount:blocks.length,itemBuilder:(context,index){returnBlockWidget(block:blocks[index]);},),),],),);}}- Hot reload to see the changes in your app:

12. Seal a class for exhaustive switching
Notice that you didn't use a wildcard or default case at the end of the last switch. Though it's good practice to always include a case for values that might fall through, it's ok in a simple example like this since you know the cases you defined account forall of the possible valuesinDays could potentially take.
When every case in a switch is handled, it's called anexhaustive switch. For example, switching on abool type is exhaustive when it has cases fortrue andfalse. Switching on anenum type is exhaustive when there are cases for each of the enum's values, too, because enums represent afixed number ofconstant values.
Dart 3 extendedexhaustiveness checking to objects and class hierarchies with the new class modifiersealed. Refactor yourBlock class as a sealed superclass.
Create the subclasses
- In
data.dart, create three new classes—HeaderBlock,ParagraphBlock, andCheckboxBlock—that extendBlock:
lib/data.dart
classHeaderBlockextendsBlock{finalStringtext;HeaderBlock(this.text);}classParagraphBlockextendsBlock{finalStringtext;ParagraphBlock(this.text);}classCheckboxBlockextendsBlock{finalStringtext;finalboolisChecked;CheckboxBlock(this.text,this.isChecked);}Each of these classes corresponds to the differenttype values from the original JSON:'h1','p', and'checkbox'.
Seal the superclass
- Mark the
Blockclass assealed. Then, refactor the if-case as a switch expression that returns the subclass corresponding to thetypespecified in the JSON:
lib/data.dart
sealedclassBlock{Block();factoryBlock.fromJson(Map<String,Object?>json){returnswitch(json){{'type':'h1','text':Stringtext}=>HeaderBlock(text),{'type':'p','text':Stringtext}=>ParagraphBlock(text),{'type':'checkbox','text':Stringtext,'checked':boolchecked}=>CheckboxBlock(text,checked),_=>throwconstFormatException('Unexpected JSON format'),};}}Thesealed keyword is aclass modifier that means you canonly extend or implement this class in the same library. Since the analyzer knows the subtypes of this class, it reports an error if a switch fails to cover one of them and isn't exhaustive.
Use a switch expression in order to display widgets
- Update the
BlockWidgetclass inmain.dartwith a switch expression that uses object patterns for each case:
lib/main.dart
classBlockWidgetextendsStatelessWidget{finalBlockblock;constBlockWidget({requiredthis.block,super.key});@overrideWidgetbuild(BuildContextcontext){returnContainer(margin:constEdgeInsets.all(8),child:switch(block){HeaderBlock(:finaltext)=>Text(text,style:Theme.of(context).textTheme.displayMedium,),ParagraphBlock(:finaltext)=>Text(text),CheckboxBlock(:finaltext,:finalisChecked)=>Row(children:[Checkbox(value:isChecked,onChanged:(_){}),Text(text),],),},);}}In your first version ofBlockWidget, you switched on a field of aBlock object to return aTextStyle. Now, you switch an instance of theBlock object itself and match againstobject patterns that represent its subclasses, extracting the object's properties in the process.
The Dart analyzer can check that each subclass is handled in the switch expression because you madeBlock a sealed class.
Try it out: Try removing one of the subclass cases from the switch expression and see the error kick in:

Also note that using a switch expression here lets you pass the result directly to thechild element, as opposed to the separate return statement needed before.
- Hot reload to see the checkbox JSON data rendered for the first time:

Try it out: Try changing the value of thechecked key in theDocument class, where you defineddocumentJson totrue instead offalse. Hot reload again to see the UI update!

13. Congratulations
You successfully experimented with patterns, records, enhanced switch and case, and sealed classes. You covered a lot of information—but only barely scratched the surface of these features. For more information on patterns, see thefeature specification.
The different pattern types, different contexts in which they can appear, and the potential nesting of subpatterns make the possibilities in behavior seeminglyendless. But they're easy to see.
You can imagine all kinds of ways to display content in Flutter using patterns. Using patterns, you can safely extract data in order to build your UI in a few lines of code.
What's next?
- Check out the documentation on patterns, records, enhanced switch and cases, and class modifiers in theLanguage section of the Dart documentation.
Reference docs
See the full sample code, step by step, in theflutter/codelabs repository.
For in-depth specifications for each new feature, check out the original design docs:
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.