The biggest advantage of Flutter development is that the entire application structure you create is made up of widgets. They make up all the visual and functional elements of Flutter applications, from the simplest buttons to the most complex designs. But what is a widget exactly? In this article, we will introduce you to some basic Flutter widgets and how to use them, as well as provide examples of how to create custom widgets.

Widget meaning: understanding the role of Flutter widgets

A widget is a basic building block of Flutter app development. Any visual component - a button, a text box, an image or a list - is a widget. Flutter distinguishes between two main types of widgets:
  • Stateless widgets: a widget whose state does not change at runtime. These are static elements such as text or icons.
  • StatefulWidget: a widget that handles dynamic state, i.e. the displayed data can change while the application is running, e.g. the input field of a form or the state of a button.

Basic Flutter Widgets

Here are some of the basic widgets you may come across in Flutter development:

1. Text

The Text widget is one of the simplest yet essential elements in Flutter applications. It displays text and can be easily customised with different styles, colours and formatting.
Usage:
Text(
  'Hello, Flutter!',
  style: TextStyle(fontSize: 20, color: Colors.blue),
);
Result:
Example of a basic Flutter widget in a mobile app - Text widget

2. Container

The Container is one of the most important layout widgets in Flutter development and can be used for almost any UI element. You can flexibly adjust its size, colour, borders, shape, shadow and other visual parameters.
Usage:
Container(
  width: 150,
  height: 150,
  padding: const EdgeInsets.symmetric(
    horizontal: 10,
    vertical: 6,
  ),
  decoration: BoxDecoration(
    color: Colors.red,
    borderRadius: BorderRadius.circular(10),
  ),
  child: const Text('Hello, Flutter!'),
);
Result:
Example of a basic Flutter widget in a mobile app - Container

3. Row and Column

The Row and Column layout widgets arrange the child widgets in rows and columns respectively, giving you the flexibility to place different elements on the screen. Their mainAxisAlignment and crossAxisAlignment parameters provide a variety of alignment options.
Usage:
Row(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Text('First item'),
    FlutterLogo(size: 50),
    Text('Third item'),
  ],
);
Result:
Example of a basic Flutter widget in a mobile app - Row layout widgets
Usage:
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Text('First item'),
    FlutterLogo(size: 50),
    Text('Third item'),
  ],
);
Result:
Example of a basic Flutter widget in a mobile app - Column layout widgets

4. ListView

ListView provides a scrollable list that is particularly useful for displaying long content such as news, shopping lists or messages.
Usage:
ListView(
  children: [
    Container(
      height: 50,
      alignment: Alignment.center,
      color: Colors.blue[300],
      child: Text('First item'),
    ),
    Container(
      height: 50,
      alignment: Alignment.center,
      color: Colors.blue[200],
      child: Text('Second item'),
    ),
    Container(
      height: 50,
      alignment: Alignment.center,
      color: Colors.blue[100],
      child: Text('Third item'),
    ),
  ],
);
Result:
Example of a basic Flutter widget in a mobile app - ListView
The standard ListView constructor is ideal for small lists. However, if you are working with a large number of items, you may want to use the ListView.builder constructor. Unlike the default, this does not create all items at once, but only as they appear on the screen as you scroll.
final items = ['First item', 'Second item', 'Third item'];
final colorCodes = [300, 200, 100];

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, index) {
      return Container(
        height: 50,
        alignment: Alignment.center,
        color: Colors.blue[colorCodes[index]],
        child: Text(items[index]),
      );
    },
  );
}

5. Scaffold

The Scaffold is a basic structure widget that provides the skeleton of a typical screen and contains elements such as an appbar (header), floating action button, drawer (side menu), and body.
Usage:
Scaffold(
  appBar: AppBar(
    title: Text('Flutter App'),
    leading: BackButton(),
  ),
  body: Center(
    child: Text('Hello, Flutter!'),
  ),
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: Icon(Icons.add),
  ),
),
Result:
Example of a basic Flutter widget in a mobile app - Scaffold

6. Stack

The Stack widget allows you to stack multiple items on top of each other, similar to layering. This allows you to arrange elements more freely, for example, overlapping widgets or moving them to different places on the screen. Precise positioning can be achieved with the Positioned widget.
Usage:
Stack(
  children: [
    Container(color: Colors.blue, width: 100, height: 100),
    Positioned(
      top: 10,
      left: 10,
      child: Container(
        color: Colors.red,
        width: 50,
        height: 50,
      ),
    ),
  ],
);
Result:
Example of a basic Flutter widget in a mobile app - Stack

Creating custom widgets

One of the main advantages of Flutter development is that you can easily create custom widgets if the existing ones don't meet your needs. It's also important for code organisation and consistency to separate UI elements that often appear on multiple screens. In this case, you can choose to create static or dynamic elements - for the former, you can use the StatelessWidget class, for the latter you can use the StatefulWidget class.

Stateless widget example

Let's start with a simple, static custom widget that creates a custom button:
class CustomButton extends StatelessWidget {
  final String text;
  final Color color;
  final VoidCallback onPressed;

  const CustomButton({super.key, required this.text, required this.color, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
      ),
      onPressed: onPressed,
      child: Text(
        text,
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}
This CustomButton widget represents a button that can be configured with text, colour and action when pressed using the text, colour and onPressed parameters. A simple use case is as follows.
CustomButton(
  text: "Custom Button",
  color: Colors.blue,
  onPressed: () {
    print("Button pressed");
  },
),
Result:
Example of a basic Flutter widget in a mobile app - CustomButton widget

Stateful widget example

Now let's look at a widget that receives an external parameter for customisation, and has an internal state that changes accordingly.
class PasswordFieldWidget extends StatefulWidget {
  final String label;

  const PasswordFieldWidget({super.key, required this.label});

  @override
  State<StatefulWidget> createState() => PasswordFieldState();
}

class PasswordFieldState extends State<PasswordFieldWidget> {
  bool hiddenPassword = true;

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      obscureText: hiddenPassword,
      decoration: InputDecoration(
        label: Text(widget.label),
        suffixIcon: IconButton(
          icon: Icon(hiddenPassword ? Icons.visibility : Icons.visibility_off),
          onPressed: () {
            setState(() => hiddenPassword = !hiddenPassword);
          },
        ),
      ),
    );
  }
}
The PasswordFieldWidget in this example is a password input field that receives the field name as a label parameter, and the hiddenPassword variable controls the visibility of the field contents. A real use case is when we want to implement a password change function in our application. In this case, we need to use two such input fields with different parameters.
Column(
  children: [
    PasswordFieldWidget(label: "Current password"),
    PasswordFieldWidget(label: "New password"),
  ],
),
Result:
Example of a basic Flutter widget in a mobile app - CustomButton widget

More common examples

  • CustomItemCard: a customised card widget, usually containing images, text and buttons in a particular style. These widgets are often used to display list items.
  • CustomInputField: A custom input field that goes beyond the functionality of the standard TextField. It has special validation, custom icons, custom background and custom appearance.
  • CustomAppBar: When implementing an application design, we often create a custom AppBar widget that provides a consistent look and feel across all screens. Common parameters for such a widget include the screen name or the action to take when the back button is pressed.

Useful tips

  • Smaller components: don't be afraid to highlight smaller components in separate widgets. This makes individual widgets easy to reuse and maintain.
  • Internal state management: When creating a state management widget, think about performance. Only update the widget when you need to.
  • Composition: in Flutter app development it is recommended to use composition instead of inheritance, i.e. compose several small widgets into one.
Building on the built-in Flutter widgets allows you to quickly create UI elements, while custom widgets allow you to create a customised and consistent user experience.
At LogiNet we can help you develop both native and Flutter mobile applications. Get in touch with our colleagues and let's talk about the best solution for you!

Let's talk about

your project

Drop us a message about your digital product development project and we will get back to you within 2 days.
We'd love to hear the details about your ideas and goals, so that our experts can guide you from the first meeting.
John Radford
Client Services Director UK