| title | Add interactivity to your app |
|---|---|
| shortTitle | Interactivity |
| description | Add simple commands to your cli application. Learn the fundamentals of Dart syntax including control flow, collections, variables, functions, and more. |
| layout | learn |
In this chapter, you'll get hands-on practice with Dart syntax. You'll learn how to read user input, print usage information, and create a basic command-line interaction.
title: What you'll accomplish items: - title: Build command handlers with if statements icon: account_tree - title: Work with lists and variables icon: data_array - title: Account for nullability in your code icon: help_outline - title: Define and call functions icon: function - title: Read and handle user input icon: keyboardBefore you begin this chapter, ensure you have:
- Completed Chapter 1 and have a working Dart development environment.
- Familiarity with basic programming concepts (variables, data types, control flow).
Add some basic functionality to your Dartpedia command-line application and then explore the Dart syntax for it.
-
Implement the
versioncommand incli/bin/cli.dart: Add logic to handle aversioncommand, which prints the current version of the CLI. Use anifstatement to check if the first argument provided isversion. You'll also need aversionconstant.First, above your
mainfunction, declare aconstvariable for the version. The value of aconstvariable can never be changed after it's been set:const version = '0.0.1'; // Add this line
Next, modify your
mainfunction to check for theversionargument:void main(List<String> arguments) { if (arguments.isEmpty) { print('Hello, Dart!'); } else if (arguments.first == 'version') { print('Dartpedia CLI version $version'); } }
The
$versionsyntax is called string interpolation. It lets you embed the value of the variable directly into a string by prefixing the variable name with a$sign. -
Test the
versioncommand: Run your application with the version argument:dart bin/cli.dart version
You should now see:
Dartpedia CLI version 0.0.1
If you run your app without arguments, you'll still see "Hello, Dart!".
-
Add a
printUsagefunction: To make the output more user-friendly, create a separate function to display usage information. Place this function outside and below yourmainfunction.void printUsage() { // Add this new function print( "The following commands are valid: 'help', 'version', 'search <ARTICLE-TITLE>'" ); }
searchis the command that will eventually search from Wikipedia. -
Implement the
helpcommand and refinemain: Now, integrate thehelpcommand using anelse ifstatement, and clean up the default behavior to call theprintUsagefunction.Modify your
mainfunction to look like this:void main(List<String> arguments) { if (arguments.isEmpty || arguments.first == 'help') { printUsage(); // Change this from 'Hello, Dart!' } else if (arguments.first == 'version') { print('Dartpedia CLI version $version'); } else { printUsage(); // Catch-all for any unrecognized command. } }
-
Understand the
if/elsestructure and variables: Now that you've implemented control flow in themainfunction, review the code that was added for it.arguments.isEmptychecks if no command-line arguments were provided.arguments.firstaccesses the very first argument, which you're using as our command.versionis declared as aconst. This means its value is known at compile time, and you can't change it during runtime.argumentsis a regular (non-constant) variable because its content can change during runtime based on user input.
Run your application with the help argument. You should see the usage information printed:
dart bin/cli.dart helpAlso, try running it without any arguments:
dart bin/cli.dart
Notice that it continues to display usage information. At this point, any command you haven't defined will also print usage information. This is expected behavior for now.
Next, implement a basic search command that takes an article title as input.
As you build this functionality, you'll work with
List manipulation, null checks, and string interpolation.
-
Integrate the
searchcommand intomain: First, modify themainfunction incli/bin/cli.dartto include anelse ifbranch that handles thesearchcommand. For now, just print a placeholder message.void main(List<String> arguments) { if (arguments.isEmpty || arguments.first == 'help') { printUsage(); } else if (arguments.first == 'version') { print('Dartpedia CLI version $version'); } else if (arguments.first == 'search') { // Add this new block: print('Search command recognized!'); } else { printUsage(); } }
-
Test the new command: Run your application with the
searchcommand:dart bin/cli.dart search
You should see:
Search command recognized!
-
Define the
searchWikipediafunction: Thesearchcommand will eventually run the core logic of your application by calling a function calledsearchWikipedia. For now, havesearchWikipediaprint the arguments passed into it with thesearchcommand. Place this new function belowmain.// ... (your existing main function) void searchWikipedia(List<String>? arguments) { // Add this new function and add ? to arguments type print('searchWikipedia received arguments: $arguments'); } // ... (your existing printUsage() function)
Highlights from the preceding code:
-
List<String>? argumentsmeans that theargumentslist itself can benull.:::note Dart enforces sound null safety, which means you have to explicitly state when a variable can be null. Any variable that isn't marked as nullable is guaranteed to never be null, even in production.
The purpose of null safety isn't to stop you from ever using null in your code, because representing the absence of a value can be valuable. Rather, it's to force you to consider nullability and therefore be more careful about it. Along with the analyzer, this helps prevent one of the most common runtime crashes in programming: null-pointer errors. :::
-
-
Call the
searchWikipediafunction from themainfunction: Now, modify thesearchcommand block inmainto callsearchWikipediaand pass it any arguments that come after thesearchcommand itself. Usearguments.sublist(1)to get all arguments starting from the second one. If no arguments are provided aftersearch, passnulltosearchWikipedia.void main(List<String> arguments) { if (arguments.isEmpty || arguments.first == 'help') { printUsage(); } else if (arguments.first == 'version') { print('Dartpedia CLI version $version'); } else if (arguments.first == 'search') { // Add this new block: final inputArgs = arguments.length > 1 ? arguments.sublist(1) : null; searchWikipedia(inputArgs); } else { printUsage(); } }
Highlights from the preceding code:
finalvariables can only be set once and are used when you never intend to change the variable again in the code.arguments.sublist(1)creates a new list containing all elements of theargumentslist after the first element (which wassearch).arguments.length > 1 ? ... : null;is a conditional (ternary) operator. It ensures that if no arguments are provided after thesearchcommand,inputArgsbecomesnull, matching the sample code's behavior forsearchWikipedia'sargumentsparameter ofList<String>?.
-
Test
searchWikipediawith arguments: Using the command line, run the application with a test article title:dart bin/cli.dart search Dart Programming
You should see:
searchWikipedia received arguments: [Dart, Programming]
Next, run the same command without the extra arguments:
dart bin/cli.dart search
You should see:
searchWikipedia received arguments: null
-
Handle the missing article title and user input with the
stdincommand: It's more user-friendly to prompt the user if they don't provide an article title on the command line. Usestdin.readLineSync()for this.First, add the necessary import at the top of your
cli/bin/cli.dartfile:import 'dart:io'; // Add this line at the top
dart:iois a core library in the Dart SDK, and provides APIs to deal with files, directories, sockets, and HTTP clients and servers, and more.Now, update your
searchWikipediafunction.void searchWikipedia(List<String>? arguments) { final String articleTitle; // If the user didn't pass in arguments, request an article title. if (arguments == null || arguments.isEmpty) { print('Please provide an article title.'); // Await input and provide a default empty string if the input is null. articleTitle = stdin.readLineSync() ?? ''; } else { // Otherwise, join the arguments into a single string. articleTitle = arguments.join(' '); } print('Current article title: $articleTitle'); }
This preceding code block introduces a few key concepts:
- It declares a
final String articleTitlevariable. This allows static analysis to detect thatarticleTitlewill be aStringand won't be null. - An
if/elsestatement then checks if command-line arguments for the search were provided. - If arguments are missing, it prompts the user,
reads input using
stdin.readLineSync(), and safely handles cases where no input is given. - If arguments are present, it uses
arguments.join(' ')to combine them into a single search string.
Highlights from the preceding code:
stdin.readLineSync() ?? ''reads the input from the user. Whilestdin.readLineSync()can return null, the null-coalescing operator (??) is used to provide an empty string ('') as a fallback if the input is null. This is a concise way to ensure that the variable is a non-null string.arguments.join(' ')concatenates all elements of theargumentslist into a single string, using a space as the separator. For example,['Dart', 'Programming']becomes"Dart Programming". This is crucial for treating multi-word command-line inputs as a single search phrase.- Dart static analysis can detect that
articleTitleis guaranteed to be initialized when the print statement is executed. No matter which path is taken through this function body, the variable is non-nullable.
- It declares a
-
Finish
searchWikipediato print mock search results: UpdatesearchWikipediato display messages that look like our program found something. This helps us see what our finished program will do without actually building everything right now. You'll only see these messages if you include a search query when you run the program.For example:
dart bin/cli.dart search Dart Programming.void searchWikipedia(List<String>? arguments) { final String articleTitle; // If the user didn't pass in arguments, request an article title. if (arguments == null || arguments.isEmpty) { print('Please provide an article title.'); // Await input and provide a default empty string if the input is null. articleTitle = stdin.readLineSync() ?? ''; } else { // Otherwise, join the arguments into the CLI into a single string articleTitle = arguments.join(' '); } print('Looking up articles about "$articleTitle". Please wait.'); print('Here ya go!'); print('(Pretend this is an article about "$articleTitle")'); }
-
Final Test Run with both scenarios:
Now that the article simulation is set up, test the
searchWikipediafunction in a few different ways:dart bin/cli.dart search Dart Programming
You should see:
Looking up articles about "Dart Programming". Please wait. Here ya go! (Pretend this is an article about "Dart Programming")
Run without arguments (type "Flutter Framework" when prompted):
dart bin/cli.dart search
Please provide an article title. Flutter Framework
You have now successfully built the basic
searchcommand with user input handling, correctly treating multi-word command-line inputs as a single search phrase in the output.
In the next chapter, you'll dive into
asynchronous programming and learn how to
fetch data from the Wikipedia API using the http package.
This will allow your application to
retrieve real data and display it to the user.