This blog is very long to read, I have summarized every bit made the video of it, and have provided all the code on GitHub, check them out:
GitHub: https://github.com/raman04-byte/flutter_docs
Video: https://youtu.be/FwBSQoKzEkE
In this blog, we are going to make Flutter Docs NavigationBar
In this whole project, I am going to follow MVC architecture
Let's talk about MVC architecture first and get to know what it is:
MVC (Model-View-Controller) is a popular architectural pattern used in software development to separate the concerns of an application into three distinct components: Model, View, and Controller. While Flutter primarily promotes using the "Flutter way" architecture (similar to the MVVM pattern), you can still implement an MVC-like structure if it aligns with your project's requirements and design principles.
Here's a brief explanation of how you can apply the MVC architecture in Flutter:
Model (M):
The Model represents the application's data and business logic. It is responsible for data storage, retrieval, and manipulation.
You can create Dart classes in a Flutter app to define your data models. These classes represent the data you are working with, such as user profiles, items in a shopping cart, or any other application-specific data.
Model classes do not depend on the UI or user interactions. They should be as independent and reusable as possible.
View (V):
The View is responsible for rendering the user interface (UI) and displaying data from the Model to the user. In Flutter, this is typically done using widgets.
Each screen or page in your Flutter app can have its own View component, which includes the UI layout and design.
Widgets like
Text
,Image
,ListView
, and custom widgets can be used to visually represent your app's screens.
Controller (C):
The Controller acts as an intermediary between the Model and the View. It handles user input, processes requests, and updates the Model or View accordingly.
In Flutter, you can use state management techniques like StatefulWidget or third-party state management libraries (e.g., Provider, Bloc, Riverpod) to implement the Controller part of MVC.
The Controller can handle user interactions, validate input, make API calls, and update the Model and View as needed.
Let's go to the development state of this navBar application:
First, let's take a look on pubspec.yaml
file:
Use the latest version of the package.
Let's look for the folder structure that we are going to follow during this development of the Navigation bar:
Let's look into main.dart
file:
import 'package:flutter/material.dart';
import 'app.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
This Flutter code is the entry point for a mobile application. It imports necessary Flutter material design widgets and a custom "app.dart" file. The main
function ensures proper initialization of the Flutter framework with WidgetsFlutterBinding.ensureInitialized()
. It then launches the app by running an instance of the MyApp
widget using runApp(const MyApp())
. The MyApp
widget, defined in "app.dart," is responsible for constructing the user interface of the application. This code sets the stage for a Flutter app, ensuring framework readiness and establishing the root widget, which is essential for building the app's graphical user interface.
Clone the repo from GitHub and try to understand the architecture
Let's dive into the development of the navigation bar:
homepage.dart
:
import 'package:docs_flutter/feature/home/template/home_template.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return const Scaffold(
body: HomeTemplate(),
);
}
}
The code defines a Flutter widget for a home page.
It imports "home_template.dart" for the home page's template and Flutter material design widgets.
The
HomePage
class extendsStatefulWidget
, representing the home page.The
createState
method returns an instance of the_HomePageState
class to manage state._HomePageState
manages the mutable state ofHomePage
.The
build
method returns aScaffold
with aHomeTemplate
as the body, structuring the home page's content.
hometemplate.dart
:
import 'package:docs_flutter/feature/home/template/home_row.dart';
import 'package:flutter/material.dart';
class HomeTemplate extends StatefulWidget {
const HomeTemplate({super.key});
@override
State<HomeTemplate> createState() => _HomeTemplateState();
}
class _HomeTemplateState extends State<HomeTemplate> {
@override
Widget build(BuildContext context) {
return const Column(
children: [
HomeRow(),
],
);
}
}
This Dart code defines a Flutter widget named
HomeTemplate
for a home page.It imports the "home_row.dart" file and Flutter's material design widgets.
The
HomeTemplate
class extendsStatefulWidget
, representing the template for the home page.
homerow.dart
:
class HomeRow extends StatefulWidget {
const HomeRow({super.key});
@override
State<HomeRow> createState() => _HomeRowState();
}
class _HomeRowState extends State<HomeRow> {
TextEditingController textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
vertical: Dimensions.scaleH(10),
horizontal: Dimensions.scaleW(5),
),
child: Row(
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: SvgPicture.asset(
Assets.image1,
height: Dimensions.scaleH(37),
),
),
const Spacer(),
DropButton(
items: const [
'Mobile',
'Web',
'Desktop',
'Embedded',
],
title: 'Multi-Platform',
height: Dimensions.scaleH(40),
width: Dimensions.scaleH(135),
menuHeight: Dimensions.scaleH(35),
),
DropButton(
items: const [
'Learn',
'Flutter Favorites',
'Packages',
'Monetization',
'Games',
'News'
],
title: 'Development',
height: Dimensions.scaleH(40),
width: Dimensions.scaleW(33),
menuHeight: Dimensions.scaleH(40),
),
DropButton(
items: const [
'Community',
'Events',
'Culture',
],
title: 'Ecosystem',
height: Dimensions.scaleH(40),
width: Dimensions.scaleW(25),
menuHeight: Dimensions.scaleH(35),
),
Padding(
padding: EdgeInsets.symmetric(
horizontal: Dimensions.scaleW(5),
),
child: Text(
'Showcase',
style: TextStyle(
color: Colors.black,
fontSize: Dimensions.scaleH(15),
),
),
),
DropButton(
items: const [
"What's new",
'Editor Support',
'Hot reload',
'Profiling',
'Install Flutter',
'DevTools',
'Cookbook',
'Codelabs'
],
title: 'Docs',
height: Dimensions.scaleH(40),
width: Dimensions.scaleW(30),
menuHeight: Dimensions.scaleH(35),
),
AnimSearchBar(
helpText: 'Search',
width: Dimensions.scaleW(40),
textController: textController,
prefixIcon: const Icon(
Icons.search,
color: Color(0xFF6e7274),
),
onSuffixTap: () {
setState(() {
textController.clear();
});
},
onSubmitted: (value) {},
),
const AppBarIcon(
icon: SimpleIcons.twitter,
),
const AppBarIcon(
icon: SimpleIcons.youtube,
),
const AppBarIcon(
icon: SimpleIcons.medium,
),
const AppBarIcon(
icon: SimpleIcons.github,
),
MouseRegion(
cursor: SystemMouseCursors.click,
child: Container(
height: Dimensions.scaleH(38),
width: Dimensions.scaleW(30),
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Color(0xFF1389fd),
),
child: Text(
"Get started",
style: TextStyle(
color: Colors.white,
fontSize: Dimensions.scaleH(15),
fontWeight: FontWeight.w500),
),
),
)
],
),
);
}
}
Let's break down the code and explain each part:
class HomeRow extends StatefulWidget
: This defines a class calledHomeRow
, which extendsStatefulWidget
. This means thatHomeRow
is a stateful widget and can have mutable state that can change over time.const HomeRow({super.key});
: This is the constructor for theHomeRow
class. It takes a named parameterkey
, which is not required, and sets it tosuper.key
. Thesuper
keyword is used to refer to the base class constructor, which is part of theStatefulWidget
class.@override State<HomeRow> createState() => _HomeRowState();
: This method is an override of thecreateState
method. It is used to create and return an instance of the state class for this widget, which is_HomeRowState
. In Flutter, the state class is where you define the mutable state and thebuild
method to describe the UI for the widget.class _HomeRowState extends State<HomeRow>
: This defines the state class for theHomeRow
widget. It extendsState<HomeRow>
and is responsible for maintaining the mutable state of the widget.TextEditingController textController = TextEditingController();
: This line declares an instance ofTextEditingController
calledtextController
. This controller is typically used to manage and control text input widgets like text fields.@override Widget build(BuildContext context) { ... }
: This is thebuild
method of the state class. It is called whenever the widget needs to be built or rebuilt. Inside this method, you define the layout and structure of the widget's UI.The UI structure consists of a
Padding
widget that wraps aRow
widget.Inside the
Row
, there are several child widgets, includingMouseRegion
,SvgPicture
,Spacer
,DropButton
,Text
,AnimSearchBar
,AppBarIcon
, andContainer
widgets.These widgets represent various UI elements, such as icons, drop-down menus, search bars, and buttons, and they are arranged in a row layout.
Various properties like dimensions, text styles, and colors are customized based on the
Dimensions
class or other constants defined in the application.
The HomeRow
widget is intended to be part of a larger Flutter app's user interface and provides a row of interactive and informational elements for the user to interact with. The specifics of how this widget behaves and interacts with the rest of the app would depend on the rest of the code in the app.
drop_button.dart
:
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import '../../../constants/dimes.dart';
class DropButton extends StatefulWidget {
final String title;
final List<String> items;
final double height;
final double menuHeight;
final double width;
const DropButton({
super.key,
required this.items,
required this.title,
required this.height,
required this.width,
required this.menuHeight,
});
@override
State<DropButton> createState() => _DropButtonState();
}
class _DropButtonState extends State<DropButton> {
String? selectedValue;
@override
Widget build(BuildContext context) {
return DropdownButton2(
underline: Container(
color: Colors.white,
),
isExpanded: true,
hint: Text(
widget.title,
style: TextStyle(
color: const Color(0xff000000),
fontSize: Dimensions.scaleH(15),
),
),
items: widget.items
.map(
(String item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: TextStyle(
fontSize: Dimensions.scaleH(13),
),
),
),
)
.toList(),
value: selectedValue,
onChanged: (String? value) {
setState(() {});
},
buttonStyleData: ButtonStyleData(
height: widget.height,
width: widget.width,
),
menuItemStyleData: MenuItemStyleData(
height: widget.menuHeight,
),
);
}
}
Let's break down the code and understand how this widget works:
Import Statements:
import 'package:dropdown_button2/dropdown_button2.dart'
imports theDropdownButton2
widget from thedropdown_button2
package.import 'package:flutter/material.dart'
imports Flutter's material design widgets, which are used to create the user interface.
Import Custom Constants:
import '../../../constants/dimes.dart'
imports constants from a local file, likely containing constants related to dimensions.
DropButton
Class:This class is a
StatefulWidget
, which means it can hold mutable state.Constructor: The constructor of this widget takes several parameters:
key
: A named parameter for the widget's key. However, there is a syntax issue here, assuper.key
is not the correct way to set the key. Instead, you can directly passKey
to the constructor and set it askey: key
.title
: A required string parameter representing the title of the dropdown.items
: A required list of strings representing the items to display in the dropdown.height
: A required double parameter representing the height of the button part of the dropdown.menuHeight
: A required double parameter representing the height of the dropdown menu.width
: A required double parameter representing the width of the button part of the dropdown.
The constructor is used to configure the initial properties of the
DropButton
.
_DropButtonState
Class:This class represents the state for the
DropButton
widget and extendsState<DropButton>
.It has a
selectedValue
property to store the currently selected item in the dropdown. It's initialized as nullable (String?
) because initially, no item is selected.The
build
the method is overridden to define the UI of theDropButton
widget.
build
Method:Inside the
build
method, aDropdownButton2
widget is created.DropdownButton2
is likely a custom dropdown widget from thedropdown_button2
package.The properties of the
DropdownButton2
widget is configured as follows:underline
: This property defines the style of the underline, and in this case, it's set to a white color.isExpanded
: The dropdown should expand to fill the available horizontal space.hint
: This is the text displayed when no item is selected in the dropdown. It uses thetitle
property from the widget and a specific text style.items
: The list of items to display in the dropdown menu. The items are constructed from thewidget.items
list and have specific text styles.value
: This is set to theselectedValue
from the widget's state, indicating the currently selected item.onChanged
: This function is called when an item is selected. It is currently empty and doesn't do anything.buttonStyleData
: This property defines the height and width of the dropdown button.menuItemStyleData
: This property defines the height of each item in the dropdown menu.
This DropButton
widget allows you to create customized dropdown buttons with various configuration options, making it easier to integrate dropdowns with specific styles and behaviors into your Flutter app.
appbar_icon.dart
:
import 'package:docs_flutter/constants/dimes.dart';
import 'package:flutter/material.dart';
class AppBarIcon extends StatefulWidget {
final IconData icon;
const AppBarIcon({super.key, required this.icon});
@override
State<AppBarIcon> createState() => _AppBarIconState();
}
class _AppBarIconState extends State<AppBarIcon> {
bool isHovered = false;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: Dimensions.scaleW(2),
),
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (event) {
setState(() {
isHovered = true;
});
},
onExit: (event) {
setState(() {
isHovered = false;
});
},
child: Icon(
widget.icon,
color: isHovered ? Colors.black : const Color(0xFF6e7274),
),
),
);
}
}
Let's break down the code to understand how this widget works:
Import Statements:
import 'package:docs_flutter/constants/dimes.dart'
imports constants related to dimensions from a local file.import 'package:flutter/material.dart'
imports Flutter's material design widgets.
AppBarIcon
Class:This class is a
StatefulWidget
, indicating that it can have mutable state.Constructor: The constructor of this widget takes two parameters:
key
: A named parameter for the widget's key.icon
: A required parameter of typeIconData
, representing the icon to be displayed.
_AppBarIconState
Class:This class represents the state for the
AppBarIcon
widget and extendsState<AppBarIcon>
.It has a
isHovered
property, which is a boolean to keep track of whether the mouse pointer is hovering over the icon.
build
Method:Inside the
build
method, the widget is defined.It consists of a
Padding
widget, which provides padding around its child.Inside the
Padding
, there is aMouseRegion
widget. TheMouseRegion
widget is used to capture mouse events and change the state when the mouse pointer enters or exits the region.The properties of the
MouseRegion
widget include:cursor
: It changes the cursor appearance to a click cursor when hovering over the region.onEnter
: This is a callback function that gets executed when the mouse pointer enters the region. In this callback, theisHovered
state is set totrue
.onExit
: This callback gets executed when the mouse pointer exits the region, and it sets theisHovered
state tofalse
.
Inside the
MouseRegion
, there is anIcon
widget. The icon's color is conditionally set based on theisHovered
state:If the mouse pointer is hovering (
isHovered
istrue
), the icon color is set toColors.black
.If not hovering, the icon color is set to a specific gray color (
Color(0xFF6e7274)
).
This AppBarIcon
widget is designed to display an icon that changes color when hovered over. It's useful for creating interactive and visually responsive icons in an app's app bar or similar navigation areas. The color change provides a visual cue to the user when interacting with the icon.
Hence our NavigationBar is completed