Before starting, let's go over what a controller is for those new.

Here's a quick summary by

{primary} The Controller is responsible for controlling the application logic and acts as the coordinator between the View and the Model. The Controller receives an input from the users via the View, then processes the user's data with the help of Model and passes the results back to the View.



class HomeController extends Controller {

  HomeController.of(BuildContext context) {
    super.context = context;

  onTapDocumentation() {

  onTapGithub() {


Material Button - onPressed()


  child: Text(
    style: Theme.of(context).textTheme.bodyText1,
  onPressed: widget.controller.onTapGithub,

If your Widget contains a controller like the home_page.dart file, you can call the controller like widget.controller.

In the example above, the controller is defined within the constructor for the StatefulPageWidget like the below example.

class MyHomePage extends StatefulPageWidget {
  final HomeController controller;
  final String title;

  MyHomePage({Key key, this.title, this.controller}) : super(key: key);

  _MyHomePageState createState() => _MyHomePageState();

Adding the controller in your router.dart file.

  name: "/",
  view: (context) => HomePage(
     controller: HomeController.of(context)

You should avoid overloading widgets with functional logic and move it to your controller. You can also consider taking advantage of a service/repository pattern if your project requires it.

Creating a controller

You can use the Metro CLI tool to create your controllers or do it manually. Here's an example.

metro make:controller profile_controller

This will create a new controller in your app/controllers directory.

Or if you are creating a new page, use the -c flag to generate a controller with the page.

metro make:page profile_page -c

Retrieving arguments from routes

When you navigate to a new page, sometimes you may want to pass in data to a new view. This might look something like the below example.

// User object
User user = new User();
user.firstName = 'Anthony';

// Pass data into the navigation'/profile', data: user);

Next, when we navigate to that page we need a way to retrieve the user. We can do this by calling; within the widget. Here’s an example below

class _ProfilePageState extends State<ProfilePage> {
  void initState() {



This should log ‘User instance’ in the console and you’ll be able to use that object in your current view.

The data parameter accepts dynamic types so you can cast the object after it’s returned.

Api Service

If you need to make an API request from within your widget, you can call the widget.controller.apiService variable to access your ApiService from your widget.

This makes it easy to quickly make an API request from anywhere in as long as your widget has a controller defined.