Flutter final vs const : the difference

There are lot of confusions about which to use and when to use. Yes, we are talking about the Dart keywords – final and const. We are more in confusion, because both are used in Flutter.

And, quite frequently.

What is the difference between “final” and “const” keywords in Flutter? Moreover. when to use them before the variables?

Let us try to find out the difference through examples. That will clear the uncertainty.

Firstly, we use the final keyword the following way in Flutter. Just watch the code snippet. We will discuss in a minute.

class PaddingCustom extends StatelessWidget {
  const PaddingCustom({
    Key? key,
    required this.check,
  }) : super(key: key);

  final List<Text> check;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: check,
      ),
    );
  }
}

As we see, this is a normal way to extract a Widget in Flutter. Normally, we keep this type of file in our “controller” folder under “lib” folder.

Subsequently, we can call this custom Widget where we need it. During calling this custom Padding Widget we will pass a list data type in its constructor like the following.

PaddingCustom(check: check),

However, when we create this custom Padding Widget, we declare the list variable as “final”. And at the same time we did not set its value in compile-time. Right?

Why did we do that?

Because we know, at the run-time, the value of the list data type variable will change. It becomes a run-time constant.

But it is not true for the “const” keyword. Neither in compile-time, or in run-time, a constant value will remain constant. It will never change once it is assigned.

Therefore, in Flutter, a Stateless widget always asks to make the variable final. And, we do not assign or set any value for that variable.

Instead, what we do?

We pass it through the class constructor. So that when an object is created we can set the value of the final variable inside the class constructor. However, once we set the value, we cannot change that value with that object again.

We can only change the value each time while creating a new object. In essence, for each object’s life cycle has one final value.

But for the “const” keyword it is not allowed. In fact, if we want to assign a value to a const variable inside a class, we need to make it static. We have discussed the “static” keyword before.

In other words, an instance variable, or property, or field, whatever you call it, should be “static” for the constant variable. To clarify, the “const” keyword does not allow us to access or modify the instance variable through an instance.

We can access it only with the Class name only.

It is not true for the “final” keyword.

Therefore, there is a lot of difference between these two keywords – final and const.

Let us try to understand the difference in a Dart program first.

After that, we will implement them in a Flutter Quiz Master App that we have built previously.

Consider a class named “AnImmutable”.

class AnImmutable {
  static const String mycConstant = 'A Constant String';
  final int myFinal; // at present it is not set so it will change later 
  
  final myDate = DateTime.now(); // at run time it will always change as time changes
  
  AnImmutable(this.myFinal);  
  
}

The above class is quite simple.

We have assigned a value to the instance variable “myConstant”. However, since we use the const keyword, we have to use the static keyword also.

Dart has not allowed us to treat the “myConstant” just like any other instance variable, or property.

However, for the other instance variable, or property “myFinal” we do not have to use the static keyword. Nor we have to assign a value as we have done in case of const.

But at the same time, we assure Dart compiler that we will assign a value to the final variable when we will create an object.

That is why we need to pass it through the class constructor.

The most interesting part of the above code is the second final variable. Why? Because we have assigned a value that will change every time, when we run it.

final myDate = DateTime.now();

Every time we run the code, the time will change. Right?

Therefore, it cannot be const. But it could be final. We hope, by now we are not in confusion anymore.

Now consider the top-level function main() where we can access these instance variables which are final.

void main() {
  
  AnImmutable anImmutable = AnImmutable(2); // setting a value which was final
  AnImmutable anotherImmutable = AnImmutable(4); // setting another value which was final
  
  
  //AnImmutable.mycConstant = 'Anything else'; // error
  // Constant variables can't be assigned a value.
  
  print(AnImmutable.mycConstant); // A Constant String
  
  print(anImmutable.myFinal); // 2
/// Now we cannot change the final variable with the object myFinal
//anImmutable.myFinal = 5; // it will throw an error
  // myFinal' can't be used as a setter because it's final.
  print(anotherImmutable.myFinal); //4
  
  print(anImmutable.myDate); // 2022-03-08 08:20:13.398   
   
}

The main difference between final and const

Let us change the previous Quiz Master App with the help of these two keywords – final and const.

Firstly, let us create a “constant.dart” file in our “model” folder under the “lib” folder.

We will use these constant Color variables in some places later.

import 'package:flutter/material.dart';

const Color primaryColor = Color(0xFF409B25);
const Color scaffoldBackgroundColor = Color(0xFF2C6F2E);
const Color appBarBackgroundColor = Color(0xFF81B165);
const Color boxDecorationColor = Color(0xFFC5DA28);
const Color elevatedButtonPrimaryColor = Color(0xFF3C9415);

Inside the “model” folder, we will also define our data source of the Quiz.

import 'question.dart';

class QuizMaster {
  int _indexNumber = 0;

  final List<Question> _quiz = [
    Question('29 - 3 = 26', true),
    Question('711 - 4 = 677', false),
    Question('455 * 3 = 1365', true),
    Question('76 / 8 = 9.5', true),
    Question('Many Thanks, press any button to end the Quiz.', true),
  ];

  void nextQuestion() {
    if (_indexNumber <= _quiz.length) {
      _indexNumber++;
    }
  }

  String getQuestion() {
    return _quiz[_indexNumber].question;
  }

  bool getAnswer() {
    return _quiz[_indexNumber].answer;
  }

  bool isFinished() {
    if (_indexNumber >= _quiz.length - 1) {
      return true;
    } else {
      return false;
    }
  }

  void reset() {
    _indexNumber = 0;
  }
}

In the above code, the final list variable will be set with each build() method, as the Flutter State will be changed with the press of the button.

Secondly, in our “controller” folder we will declare a custom Padding Widget that uses a final List variable.

Remember, it will also change later as we check whether the answer is right or wrong.

import 'package:flutter/material.dart';

class PaddingCustom extends StatelessWidget {
  const PaddingCustom({
    Key? key,
    required this.check,
  }) : super(key: key);

  final List<Text> check;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.center,
        children: check,
      ),
    );
  }
}

When the quiz is over, the final list variable will be empty.

The main action will take place in the “quiz_app.dart” file in “view” folder, where we have used a Stateful widget. In addition, we have defined our business logic there.

But before that, we go step by step. First, the root Widget.

import 'package:flutter/material.dart';
import 'model/quiz_master.dart';
import 'view/quiz_app.dart';

QuizMaster quizMaster = QuizMaster();

void main() => runApp(const QuizApp());

Next, in the “view” folder, we have used the ThemeData using the constant Color variables.

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';

import '../model/constant.dart';
import 'quiz_page.dart';

class QuizApp extends StatelessWidget {
  const QuizApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(

// we're using the const Color variables here defined in "model" folder
        primaryColor: primaryColor,
        scaffoldBackgroundColor: scaffoldBackgroundColor,
      ),
      home: Scaffold(
        appBar: AppBar(

// we're using the const Color variables here defined in "model" folder
          backgroundColor: appBarBackgroundColor,
          title: Text(
            'Mathematical Quiz',
            style: GoogleFonts.lacquer(
              textStyle: TextStyle(
                color: Colors.purple.shade50,
                fontSize: 20.0,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
        body: const SafeArea(
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 10.0),
            child: QuizPage(),
          ),
        ),
      ),
    );
  }
}

Finally, the “QuizApp” Widget, of which we have already said before.

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:quiz_master/main.dart';
import 'package:rflutter_alert/rflutter_alert.dart';

import '../controller/padding_custom.dart';
import '../model/constant.dart';

class QuizPage extends StatefulWidget {
  const QuizPage({Key? key}) : super(key: key);

  @override
  _QuizPageState createState() => _QuizPageState();
}

class _QuizPageState extends State<QuizPage> {
  List<Text> check = [];

  void checkAnswer(bool userPickedAnswer) {
    bool correctAnswer = quizMaster.getAnswer();

    setState(() {
      if (quizMaster.isFinished() == true) {
        Alert(
          context: context,
          title: 'Quiz Completed.',
          desc:
              'We\'ve reached the end. Thanks for taking part. Meet you again.',
        ).show();

        quizMaster.reset();

        check = [];
      } else {
        if (userPickedAnswer == correctAnswer) {
          check.add(
            Text(
              'You\'re Right. Well Done.',
              style: GoogleFonts.laila(
                textStyle: const TextStyle(
                  fontSize: 20.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          );
        } else {
          check.add(
            Text(
              'You\'re Wrong. Try Again.',
              style: GoogleFonts.laila(
                textStyle: const TextStyle(
                  fontSize: 20.0,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
          );
        }
        quizMaster.nextQuestion();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      top: true,
      child: Center(
        child: ListView(
          //shrinkWrap: true,
          children: <Widget>[
            Container(
              margin: const EdgeInsets.fromLTRB(20.0, 5.0, 20.0, 5.0),
              alignment: Alignment.center,
              child: Text(
                quizMaster.getQuestion(),
                style: GoogleFonts.lalezar(
                  textStyle: const TextStyle(
                    fontSize: 30.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            checkingAnswer('Correct', true),
            const SizedBox(
              height: 10,
            ),
            checkingAnswer('Wrong', false),

// we're using the final List variable check here defined in "controller" folder
            PaddingCustom(check: check),
          ],
        ),
      ),
    );
  }

  Container checkingAnswer(String corerctOrWrong, bool trueOrFalse) {
    return Container(
      padding: const EdgeInsets.all(5.0),
      decoration: BoxDecoration(

// we're using the const Color variables here defined in "model" folder
        color: boxDecorationColor,
        borderRadius: BorderRadius.circular(10.0),
      ),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(

// we're using the const Color variables here defined in "model" folder
          primary: elevatedButtonPrimaryColor,
        ),
        onPressed: () {
          checkAnswer(trueOrFalse);
        },
        child: Text(
          corerctOrWrong,
          style: GoogleFonts.laila(
            textStyle: const TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    );
  }
}

As a result, we have successfully refactored the Quiz Master App with the help of const and final variables.

Now, as we run the App, we get the same result.


Material Theme Color Greenish
Material Theme Color Greenish

We have used the most of the code snippet to distinguish between the const and final keywords.

Still, if you want to clone the whole project to understand the Flutter Model-View-Controller structure, please use this GitHub repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

Courses at Educative

GitHub repository

Technical blog

Twitter

Comments

Leave a Reply