Skip to content

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.

  1. 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.
  2. 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!'),
        ),
      ),
    );
  }
}
  1. 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.

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

  1. Stateless Widgets: These are like pictures. They don’t change.
  2. 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). A StatefulWidget 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 of MyApp 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 call setState(), 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')
),
Or use string interpolation and add some text to the 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.

Russian doll

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 example

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.

row in col

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),
        ],
      ),
    ));
  }
}
Explanation:

  • 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 the TextField, 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 temperature converter

  // 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]),
          );
        },
      ),
    );
  }
}
Explanation:

  • We define a list of strings called shoppingList globally outside of any class. It contains three items: Lipstick, T-shirt, and Hairband.
  • 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

shopping-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]),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}
Explanation:

  • We wrapped the ListTile with the Dismissible 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. plot 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  
Press the button pub get or run 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.

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.

counter list

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.