EthicsComputerCourses
Join training +91 90532 10052

Old vs New Flutter

Flutter & Dart · Lesson 28 of 29 Advanced 7 min read Updated

Flutter moves fast. A tutorial or Stack Overflow answer from a few years ago may use buttons that no longer exist, code without null safety, or theme properties that were renamed. Copying such code into a Flutter 3.47 project gives errors or a wall of deprecation warnings, and beginners often think they made a mistake.

This lesson gives you a short history of Flutter and Dart, a table of old APIs and their modern replacements, and the tools that update old code for you. Use it as a reference whenever you meet unfamiliar older code.

A short timeline

WhenReleaseWhat changed for developers
December 2018Flutter 1.0First stable release, mobile only; Dart 2 without null safety
March 2021Flutter 2.0, Dart 2.12Sound null safety by default; web support stable; new button classes
May 2022Flutter 3.0Stable support for all six platforms
May 2023Flutter 3.10, Dart 3.0100% sound null safety required; records, patterns, sealed and other class modifiers
November 2023Flutter 3.16Material 3 becomes the default design
December 2024Flutter 3.27Impeller default on Android API 29+; Row and Column get spacing; Color.withOpacity deprecated
November 2025Flutter 3.38, Dart 3.10Dot shorthands such as .center
August 2026Flutter 3.47, Dart 3.13Primary constructors; material_ui and cupertino_ui packages available as an opt-in

Flutter now ships a stable release roughly every three months, so expect a few deprecations with each one. Deprecated APIs usually keep working for a while (with a warning) and are removed in a later release.

The null safety migration

Code written before Dart 2.12 has no ? in its types, because every type could hold null. You will recognise it by patterns like these:

Diff
- String name;                 // could silently be null
- int count = map['count'];    // no null check
- MyWidget({Key key, this.title}) : super(key: key);
+ String? name;                // nullable only when it really can be missing
+ int count = map['count'] ?? 0;
+ const MyWidget({super.key, required this.title});

The old style also used @required (from package:meta) instead of the required keyword, and constructors passed key manually with : super(key: key) instead of the newer super.key super parameter (Dart 2.17).

During the move to null safety, Dart offered a tool called dart migrate, and files could opt out with a // @dart=2.9 comment. Since Dart 3.0 (May 2023) all code must be null safe: the migration tool was removed, opt-out comments no longer work, and packages that were never migrated cannot be used. If a tutorial's code has no ? anywhere, translate it using Variables & Sound Null Safety rather than trying to run it as is.

Old widgets and APIs, and their replacements

Old (you may see)New (use this)Status
FlatButton, RaisedButton, OutlineButtonTextButton, ElevatedButton, OutlinedButton (and FilledButton)Old ones removed
Scaffold.of(context).showSnackBar(...)ScaffoldMessenger.of(context).showSnackBar(...)Old one removed
WillPopScope(onWillPop: ...)PopScope(canPop: ..., onPopInvokedWithResult: ...)Deprecated (Android predictive back)
ButtonBarOverflowBarDeprecated
MaterialStateProperty, MaterialStateWidgetStateProperty, WidgetStateDeprecated aliases
color.withOpacity(0.5)color.withValues(alpha: 0.5)Deprecated
textTheme.headline1, bodyText1 ...displayLarge, bodyLarge ... (Material 3 type scale)Old names removed
ColorScheme.background, onBackgroundsurface, onSurfaceDeprecated
accentColor, primarySwatch-based themesColorScheme.fromSeed(...)accentColor removed
useMaterial3: trueNothing; Material 3 is the defaultNo longer needed
MediaQuery.of(context).textScaleFactorMediaQuery.textScalerOf(context)Deprecated
MediaQuery.of(context).sizeMediaQuery.sizeOf(context)Old way works; new way rebuilds less
Radio(groupValue: ..., onChanged: ...)RadioGroup around Radio widgetsDeprecated
DropdownButtonFormField(value: ...)DropdownButtonFormField(initialValue: ...)Deprecated
CardTheme(...), DialogTheme(...) in ThemeDataCardThemeData(...), DialogThemeData(...)Changed

Here is a typical piece of 2020-era code and its modern version:

Diff
- RaisedButton(
-   color: Theme.of(context).accentColor,
-   onPressed: _save,
-   child: Text('Save', style: Theme.of(context).textTheme.button),
- )
+ FilledButton(
+   onPressed: _save,
+   child: const Text('Save'),
+ )

The new button takes its colour and text style from the Material 3 theme automatically, so most of the old styling code simply disappears. If you need to style it, use FilledButton.styleFrom(...) or a filledButtonTheme (see Material 3 Theming).

Language features older code does not use

Older code also predates many Dart features you have learned. It still compiles, but modern code is shorter and safer:

Dart
// Old style
String gradeOld(int marks) {
  if (marks >= 90) {
    return 'A+';
  } else if (marks >= 75) {
    return 'A';
  }
  return 'B';
}

List<Object> splitOld(String fullName) {
  final parts = fullName.split(' ');
  return [parts.first, parts.length];
}

// Modern style (Dart 3)
String grade(int marks) => switch (marks) {
  >= 90 => 'A+',
  >= 75 => 'A',
  _ => 'B',
};

(String, int) split(String fullName) {
  final parts = fullName.split(' ');
  return (parts.first, parts.length);
}

void main() {
  print('${gradeOld(92)} ${grade(92)}');
  final old = splitOld('Ananya Rao');
  final (first, words) = split('Ananya Rao');
  print('${old[0]} ${old[1]} | $first $words');
}
Output
A+ A+
Ananya 2 | Ananya 2

The old function returns a List<Object>, so the caller loses the types and must cast; the record (String, int) keeps them. Other things to modernise when you see them: MainAxisAlignment.center can become .center (dot shorthands, Dart 3.10), a SizedBox between Row/Column children can become spacing:, and AnimatedBuilder used only to listen to a model can become ListenableBuilder.

Let the tools do the work

You rarely need to update old code by hand:

Terminal
flutter analyze             # lists deprecated API uses and other issues
dart fix --dry-run          # shows the automatic fixes it can apply
dart fix --apply            # applies them to the whole project
flutter pub outdated        # shows which packages are behind
flutter upgrade             # updates the Flutter SDK itself

dart fix knows many Flutter migrations (it reads fix rules that the Flutter team ships with the SDK), so it can rename deprecated APIs such as MaterialState to WidgetState across your project in one step. Review the changes with Git before committing. For bigger changes, read the breaking changes page on docs.flutter.dev, which has a migration guide for every breaking change, release by release.

Common mistakes

  • Copying old tutorial code blindly. Check the date; anything before 2021 lacks null safety and uses old buttons.
  • Ignoring deprecation warnings. Today's warning is next year's compile error. Fix them while they are few.
  • Upgrading Flutter without running tests. Upgrade, run dart fix --apply, flutter analyze and flutter test, then commit.
  • Pinning a very old Flutter version forever. Store requirements (such as target Android API levels) eventually force you to upgrade.

Key points

  • Flutter 2 (2021) brought sound null safety; Dart 3 (2023) made it mandatory and added records and patterns.
  • Material 3 has been the default since Flutter 3.16; old headline1-style text names are gone.
  • Old buttons and Scaffold.of(...).showSnackBar were removed; WillPopScope, ButtonBar, withOpacity and MaterialState are deprecated.
  • Modern Dart prefers switch expressions, records, super.key, dot shorthands and spacing.
  • flutter analyze and dart fix --apply find and fix most deprecated code automatically.

Exercise

Modernise this 2020-era widget. Make it null safe, use super.key, replace the old button, theme and text style APIs, and keep the behaviour (show the title and a save button that calls onSave).

class SaveBar extends StatelessWidget { final String title; final VoidCallback onSave; SaveBar({Key key, @required this.title, this.onSave}) : super(key: key); Widget build(BuildContext context) { return ButtonBar(children: [Text(title, style: Theme.of(context).textTheme.headline6), RaisedButton(color: Theme.of(context).accentColor, onPressed: onSave, child: Text('Save'))]); } }

Show solution
Dart
import 'package:flutter/material.dart';

class SaveBar extends StatelessWidget {
  const SaveBar({super.key, required this.title, this.onSave});

  final String title;
  final VoidCallback? onSave;

  @override
  Widget build(BuildContext context) {
    return OverflowBar(
      alignment: MainAxisAlignment.end,
      spacing: 8,
      children: [
        Text(title, style: Theme.of(context).textTheme.titleLarge),
        FilledButton(onPressed: onSave, child: const Text('Save')),
      ],
    );
  }
}

onSave becomes VoidCallback?, because the old code did not require it; a null value simply disables the button. headline6 maps to titleLarge in the Material 3 type scale, ButtonBar becomes OverflowBar, and FilledButton takes its colour from the theme's primary colour, so accentColor is no longer needed.

Quick Quiz

1. Since which Dart version must all code be null safe?

2. What replaces RaisedButton?

3. Which widget replaces WillPopScope?

4. Which command applies automatic fixes for many deprecated APIs?

Learn Flutter & Dart with a mentor

Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call