2. Flutter
Flutter is a framework for building natively compiled applications for mobile, web, and desktop from a single codebase. It allows you to create beautiful and fast apps with ease.
To start using Flutter on your computer, follow the installation guide provided in the official Flutter installation documentation. This will walk you through installing Flutter on your machine and setting up the necessary tools.
If you don't have the necessary permissions to install Flutter locally, you can quickly start coding with Flutter in the cloud using Zapp. This platform lets you write, run, and test Flutter apps directly from your browser.
2.1 Your First Flutter App: "Hello, World!"
Here’s how to create your first Flutter app that displays "Hello, World!" on the screen.
-
Create a New Flutter Project
- If you're working locally, run
flutter create hello_world
in your terminal. This creates a new Flutter project. - If you're using Zapp, just open the editor and replace the existing code with the following.
- If you're working locally, run
-
Replace the Default Code with:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Hello World in Flutter'),
),
body: Center(
child: Text('Hello, World!'),
),
),
);
}
}
-
Run the App
- If you are running it locally, use
flutter run
in your terminal. - In Zapp, simply hit the
Run
button to see the output.
- If you are running it locally, use
Explanation
MyApp
: The main widget that runs the app.MaterialApp
: A wrapper widget that applies Material Design to your app.Scaffold
: Provides the structure for the visual interface, including the app bar and body.Center
: A widget that centers its child widget on the screen.Text
: Displays the text "Hello, World!" in the center of the screen.
Widget
In Flutter everything is a widget. Widgets are immutable, meaning they don't change themselves. So when the UI needs to be updated (for example, after a user interaction), Flutter calls the build method to rebuild the widget and display the updated UI.
first app
Modify the app and understand what each part does.
2.2 Stateful Widgets and Interaction
In Flutter, state refers to data or information that can change in your app. Think of it like the current situation or status of something in your app. For example:
- A button that changes color when pressed
- A counter that increases every time you press a button
In Flutter, we have two types of widgets
- Stateless Widgets: These are like pictures. They don’t change.
- Stateful Widgets: These are like a clock showing the current time. They can change over time.
2.2.1 Counter App
Now, let’s build a Counter App where a number increases every time the user presses a button.
Here’s the full code with comments explaining what’s happening:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp()); // Start the app
}
// MyApp is a StatefulWidget because its counter can change
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState(); // Create the state of MyApp
}
class _MyAppState extends State<MyApp> {
int _counter = 0; // This is our counter (starts at 0)
// This method runs when the button is pressed
void _incrementCounter() {
setState(() {
_counter++; // Increase the counter by 1
});
}
@override
Widget build(BuildContext context) {
// This builds the user interface of the app
return MaterialApp(
home: Scaffold(
body: Text(
'$_counter', // Show the current counter value
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter, // Call the method to increase the counter when pressed
child: Icon(Icons.add), // Button icon (a plus sign)
),
),
);
}
}
Copy the code into Zapp and run it.
Explanation of the Code
MyApp
is a StatefulWidget because the app has a changing value (the counter). AStatefulWidget
is like a person with a changing mood. The widget can update its state (mood) whenever needed, and Flutter will rebuild the UI to reflect the new mood._MyAppState
is where the state ofMyApp
is stored. In our case, the state is the counter value, which starts at 0.- The
setState()
method is like telling Flutter that something has changed (like saying, "Hey, the counter just went up!"). When you callsetState()
, Flutter knows it should update the screen to show the new counter value. - The
build()
method defines the UI, which is the visual part of your app. It says, "Here’s what the screen looks like!" - The
FloatingActionButton
is the button you press. Every time you press it, it calls_incrementCounter()
, which updates the counter.
layout
Make the app nicer, e.g. place the text widget into a center widget
Center(
child:Text($_counter')
),
2.2.2 Widgets
In Flutter, everything you see on the screen is a widget. Think of a widget as a building block or a part of the user interface (UI). Widgets can be things like buttons, text, images, or entire layouts.
Widgets are like the ingredients of a recipe. When you combine them in different ways, you create your app’s interface.
For example
- A Text widget shows some text on the screen.
- A Container widget is like a box that can hold other widgets, and you can customize it with colors, padding, margins, etc.
2.2.3 Simple Widgets
The Text
widget is one of the simplest and most commonly used widgets in Flutter. It simply displays a string of text on the screen.
Text('Hello, Flutter!')
This displays "Hello, Flutter!" on the screen.
2.2.4 Container Widget
A Container
is a flexible and powerful widget. You can think of it as a box that can hold other widgets. You can add things like
- Padding (space around the content inside the box)
- Margin (space around the box itself)
- Color (to give the box a background color)
- Width/Height (to control the size of the box)
Container(
color: Colors.blue,
padding: EdgeInsets.all(16),
child: Text('This is inside a container'),
)
Here, the Text
widget is placed inside the Container
widget. The Container
adds some padding and a background color around the text.
2.2.5 Nested Widgets
In Flutter, widgets can be nested inside each other, similar to Russian nesting dolls (Matryoshka dolls). Just like how one doll fits inside another, each Flutter widget can contain other widgets within it, allowing you to build more complex UI layouts.
For example, a Column
widget might contain several Text
widgets inside it, just like how a larger Russian doll can house progressively smaller dolls within it. This nesting structure helps you create hierarchical, layered designs, where each widget is responsible for a particular part of the layout. And just like the dolls, each widget can be independently customized and reused as needed.
Example of nesting:
Container(
color: Colors.yellow,
padding: EdgeInsets.all(10),
child: Container(
color: Colors.green,
padding: EdgeInsets.all(20),
child: Text('This text is inside two containers!'),
),
)
2.2.6 Rows and Columns
When building layouts, you often need to arrange widgets in a line (horizontally or vertically). This is where Row and Column widgets come in.
2.2.6.1 Column Widget
A Column
is a widget that arranges other widgets vertically, one below the other.
Example:
Column(
children: [
Text('First Item'),
Text('Second Item'),
Text('Third Item'),
],
)
Here, the Column
arranges the three Text
widgets vertically.
2.2.6.2 Row Widget
A Row
is similar, but it arranges widgets horizontally, side by side.
Example:
Row(
children: [
Text('First Item'),
Text('Second Item'),
Text('Third Item'),
],
)
This Row
will display the three Text
widgets horizontally.
2.2.7 Combining Rows and Columns
You can also nest Rows
inside Columns
, or Columns
inside Rows
, to create more complex layouts.
Column(
children: [
Row(
children: [
Text('Item 1'),
Text('Item 2'),
],
),
Text('Item 3'),
],
)
For more information on widgets, check out the Layout section.
Zapp: Format, ...
You can simply right-click inside the code window in Zapp and select "Format Document" to automatically format your code.
Layout
Try to implement a nice layout of your choice. Start simple.
2.3 User Input
So far, we’ve built a simple counter app, which was fun! But what if we want to get input from the user and do something with it? For example, let’s build a simple Temperature Converter that takes a temperature in Celsius from the user and converts it to Fahrenheit.
When we create apps, we often need to ask the user to type something in or choose something. In Flutter, we can easily handle this with TextField widgets and buttons.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// This creates a TextEditingController object that will be used to manage the text input in a TextField widget.
final TextEditingController _controller = TextEditingController();
String _result = '';
void _convertTemperature() {
// Get the Celsius value entered by the user
double celsius = double.tryParse(_controller.text) ?? 0;
double fahrenheit = (celsius * 9 / 5) + 32;
// Change the state and hence trigger a redrawing of the UI, i.e. trigger a call of the method build
setState(() {
_result = '$celsius °C = $fahrenheit °F';
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
TextField(controller: _controller),
ElevatedButton(
onPressed: _convertTemperature,
child: Text('Convert'),
),
Text(_result),
],
),
));
}
}
- TextField: This is where the user enters their input. We use a
TextEditingController
to get the text that the user types. - Button: When the user presses the Convert button, the
_convertTemperature()
function is called. It reads the value from theTextField
, converts it from Celsius to Fahrenheit, and displays the result. : - The result (the conversion) is displayed below the button in a
Text
widget.
Check the same program with a nicer UI
// just the ui part, everything else as before
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Celsius to Fahrenheit Converter'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Enter temperature in Celsius',
border: OutlineInputBorder(),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _convertTemperature,
child: Text('Convert'),
),
SizedBox(height: 20),
Text(
_result,
style: TextStyle(fontSize: 24),
),
],
),
),
),
);
}
User Input
Try to implement similar functions, e.g. a currency converter.
2.3.1 Age Calculator with DatePicker
In this example, we'll introduce a very simple way for users to select their birthdate using a calendar. After they select a date, the app will automatically calculate their age based on the current date.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime? _selectedDate;
String _age = '';
// Function to show the date picker
Future<void> _pickDate() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(), // Default to the current date
firstDate: DateTime(1900), // Earliest date
lastDate: DateTime.now(), // Latest date
);
if (picked != null && picked != _selectedDate) {
setState(() {
_selectedDate = picked;
_calculateAge();
});
}
}
void _calculateAge() {
if (_selectedDate == null) return;
DateTime currentDate = DateTime.now();
int age = currentDate.year - _selectedDate!.year;
// Adjust if the birthday hasn't occurred yet this year
if (currentDate.month < _selectedDate!.month ||
(currentDate.month == _selectedDate!.month && currentDate.day < _selectedDate!.day)) {
age--;
}
// set the state and hence trigger refreshing the ui
setState(() {
_age = 'Your age is $age years';
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.calendar_today),
onPressed: _pickDate,
),
Text(_age),
],
),
),
);
}
}
2.4 Simple Shopping List
We want to implement a simple shopping list.
import 'package:flutter/material.dart';
// Define the shopping list globally
List<String> shoppingList = [
'Lipstick',
'T-shirt',
'Hairband',
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ShoppingListScreen(),
);
}
}
class ShoppingListScreen extends StatefulWidget {
@override
_ShoppingListScreenState createState() => _ShoppingListScreenState();
}
class _ShoppingListScreenState extends State<ShoppingListScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Shopping List'),
),
body: ListView.builder(
itemCount: shoppingList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(shoppingList[index]),
);
},
),
);
}
}
- We define a list of strings called
shoppingList
globally outside of any class. It contains three items:Lipstick
,T-shirt
, andHairband
. - We display the shopping list using a
ListView.builder()
. This allows the list to scroll if there are more items.
2.4.1 Add an Item to the List
Next, we'll allow the user to add an item to the shopping list. To do this, we will add a simple TextField and a Button to submit the new item. We also need a StatefulWidget, because the list will change dynamically, and we need to update the ListView whenever the list changes.
import 'package:flutter/material.dart';
// Define the shopping list globally
List<String> shoppingList = [
'Lipstick',
'T-shirt',
'Hairband',
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ShoppingListScreen(),
);
}
}
class ShoppingListScreen extends StatefulWidget {
@override
_ShoppingListScreenState createState() => _ShoppingListScreenState();
}
class _ShoppingListScreenState extends State<ShoppingListScreen> {
final TextEditingController _controller = TextEditingController();
// Function to add item to the list
void _addItem() {
setState(() {
shoppingList.add(_controller.text);
});
_controller.clear(); // Clear the input field
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Shopping List'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Add new item',
border: OutlineInputBorder(),
),
),
),
ElevatedButton(
onPressed: _addItem,
child: Text('Add Item'),
),
Expanded(
child: ListView.builder(
itemCount: shoppingList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(shoppingList[index]),
);
},
),
),
],
),
);
}
}
2.4.2 Swipe to Delete Items
Now, let’s introduce swipe-to-delete. We'll wrap the ListTile
with a Dismissible
widget, which will allow users to swipe left or right to remove an item.
import 'package:flutter/material.dart';
// Define the shopping list globally
List<String> shoppingList = [
'Lipstick',
'T-shirt',
'Hairband',
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ShoppingListScreen(),
);
}
}
class ShoppingListScreen extends StatefulWidget {
@override
_ShoppingListScreenState createState() => _ShoppingListScreenState();
}
class _ShoppingListScreenState extends State<ShoppingListScreen> {
final TextEditingController _controller = TextEditingController();
void _addItem() {
setState(() {
shoppingList.add(_controller.text);
});
_controller.clear(); // Clear the input field
}
// Function to handle item removal
void _removeItem(int index) {
setState(() {
shoppingList.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Shopping List'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Add new item',
border: OutlineInputBorder(),
),
),
),
ElevatedButton(
onPressed: _addItem,
child: Text('Add Item'),
),
Expanded(
child: ListView.builder(
itemCount: shoppingList.length,
itemBuilder: (context, index) {
return Dismissible(
key: Key(shoppingList[index]), // Unique key for each item
onDismissed: (direction) {
_removeItem(index); // Remove the item when swiped
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Item removed')),
);
},
child: ListTile(
title: Text(shoppingList[index]),
),
);
},
),
),
],
),
);
}
}
- We wrapped the
ListTile
with theDismissible
widget, which allows users to swipe left or right to delete an item. - The
onDismissed
callback is triggered when the item is swiped, and it calls the_removeItem()
function to remove the item from the list. - After the item is deleted, we show a SnackBar as a small message at the bottom to confirm that the item was removed.
ToDo-List
Instead of a shopping list, implement a todo list. Adapt the layout to your needs.
2.5 Painting Mathematical Functions
This example will focus on a simple function plotting scenario, where we plot a mathematical function like (y = x^2) (a basic parabola), and allow the user to see how the function is visualized on the screen.
We will use the library fl_chart. To use the fl_chart package in your Flutter project, you need to first add it to your pubspec.yaml file under dependencies.
Open the pubspec.yaml file and make sure to add fl_chart just below the cupertino_icons package (or wherever you want, but usually it’s under the dependencies section).
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2 # This is usually already there
fl_chart: any
flutter pub get
in the terminal.
import 'package:flutter/material.dart';
// to be able to use the chart in the code we need to import it
import 'package:fl_chart/fl_chart.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ParabolaChart(), // Our custom widget that displays the parabola chart
)
);
}
}
class ParabolaChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
// Controls grid lines' appearance in the background
gridData: FlGridData(show: true),
// Data for the line chart (the parabola)
lineBarsData: [
LineChartBarData(
spots: _generateParabolaPoints(), // Points to plot on
),
],
),
);
}
// This function generates the points for the parabola (y = x^2)
List<FlSpot> _generateParabolaPoints() {
List<FlSpot> spots = []; // List to hold the points of the parabola
for (double x = -5; x <= 5; x += 0.1) {
double y = x * x;
spots.add(FlSpot(x, y)); // Add each (x, y) point to the list
}
return spots;
}
}
Functions
Plot another function, change the interval, and adjust the step size.
Pie Chart
Use the pie chart of fl_chart.
2.6 Navigation
Most apps feature a bottom navigation bar for easy navigation between different sections. Here's a simple example that implements a bottom navigation bar, where one page shows a counter and the other displays a list with detailed navigation.


Here’s the code. Copy it into Zapper, run it, explore how it works, and feel free to modify it as needed.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Set the home screen of the app
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// This variable keeps track of the selected tab in the BottomNavigationBar
int _selectedIndex = 0;
// List of items to display in the ListScreen
final List<String> _items = List.generate(20, (index) => 'Item $index');
// This method handles the BottomNavigationBar tab change
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
// Private method to return the selected screen based on the _selectedIndex
Widget _getSelectedScreen() {
if (_selectedIndex == 0) {
return CounterScreen();
} else {
return ListScreen(items: _items);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// Display different screens based on the selected tab
body: _getSelectedScreen(), // Using the private method here
// BottomNavigationBar to navigate between screens
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped, // Call this method when a tab is tapped
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add),
label: 'Counter', // First tab for the counter
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'List', // Second tab for the list
),
],
),
);
}
}
// Counter Screen with FloatingActionButton
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
// The counter variable to keep track of the value
int _counter = 0;
// Method to increment the counter when the button is pressed
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
// Display the counter value in the center of the screen
body: Center(
child: Text('Counter: $_counter', style: TextStyle(fontSize: 24)),
),
// FloatingActionButton that increments the counter
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter, // Call _incrementCounter when pressed
child: Icon(Icons.add), // Icon for the floating action button
),
);
}
}
// List Screen with navigation to the details screen
class ListScreen extends StatelessWidget {
final List<String> items;
// Constructor to pass the list of items to the ListScreen
ListScreen({required this.items});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Item List')),
// ListView to display a list of items
body: ListView.builder(
itemCount: items.length, // Number of items in the list
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]), // Display the item name
onTap: () {
// Navigate to the details screen when an item is tapped
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(item: items[index]),
),
);
},
);
},
),
);
}
}
// Details Screen without Bottom Navigation
class DetailsScreen extends StatelessWidget {
final String item;
// Constructor to pass the selected item to the DetailsScreen
DetailsScreen({required this.item});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
// Display the details for the selected item in the center
body: Center(
child: Text('Details for $item', style: TextStyle(fontSize: 24)),
),
);
}
}
For more complex apps and to preserve the state of a page (e.g. counter) it might be reasonable to use the lib go_router. A well described example may be found here Flutter Bottom Navigation Bar with Stateful Nested Routes using GoRouter.
2.7 Games
Flame is a game engine built on top of Flutter. It is lightweight and makes it easy to create 2D games. Flame simplifies many complex game development tasks, such as handling animations, sprites, and physics.
Flame: Local
Since Flame is still in development, many new features may not be available in Zapp. Therefore, it's recommended to use a local installation for developing games in Flutter.
Create a new blank Flutter project and, run the following command in your project's terminal (make sure you're in the project directory where your pubspec.yaml
file is located):
flutter pub add flame
You should see something like this in pubspec.yaml
:
dependencies:
flame: any
Understand the Game Widget - a component used to integrate a game into any Flutter app - and the Game Loop, which automatically invokes methods for players and other game objects while rendering the screen frame by frame.
Then, choose one of the tutorials Flame Tutorials and start coding.
For each line or block of code you don't understand, ask any AI, such as ChatGPT, Perplexity, github copilot, etc., using prompts like
Please be my motivating tutor and explain the following code, line by line.
Help me understand the overall picture, and use analogies that are easy to relate to for a teenager where appropriate.
Keep the tone encouraging and make sure I can follow along easily.
2.8 Next Steps
Great! Now you're ready to follow Google's official Flutter learning path and Codelabs or start building your own app. Feel free to use my detailed material on Flutter and Human-Computer Interaction to deepen your understanding. You can also explore the documentation and resources like the cookbook to learn on demand and gain practical experience.