How to use Functions of another File in Dart / Flutter?

I have a Flutter app where I'm using the flutter_web_view package. I'm using it over several different files and would love to create its own file and simply reference with the _launchwebview function anywhere in my app because there are several lines of code needed in order to make it work. I know how to reference files and pass information but not methods/functions. Here is the class code...

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

class ShopClass extends StatefulWidget {
  @override
  ShopClassState createState() => new ShopClassState();
}

class ShopClassState extends State<ShopClass> {
  String _redirectedToUrl;
  FlutterWebView flutterWebView = new FlutterWebView();
  bool _isLoading = false;

  @override
  initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Widget leading;
    if (_isLoading) {
      leading = new CircularProgressIndicator();
    }
    var columnItems = <Widget>[
      new MaterialButton(
          onPressed: launchWebViewExample, child: new Text("Launch"))
    ];
    if (_redirectedToUrl != null) {
      columnItems.add(new Text("Redirected to $_redirectedToUrl"));
    }
    var app = new MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          leading: leading,
        ),
        body: new Column(
          children: columnItems,
        ),
      ),
    );
    return app;
  }


  void launchWebViewExample() {
    if (flutterWebView.isLaunched) {
      return;
    }

    flutterWebView.launch("https://apptreesoftware.com",
        headers: {
          "X-SOME-HEADER": "MyCustomHeader",
        },
        javaScriptEnabled: false,
        toolbarActions: [
          new ToolbarAction("Dismiss", 1),
          new ToolbarAction("Reload", 2)
        ],
        barColor: Colors.green,
        tintColor: Colors.white);
    flutterWebView.onToolbarAction.listen((identifier) {
      switch (identifier) {
        case 1:
          flutterWebView.dismiss();
          break;
        case 2:
          reload();
          break;
      }
    });
    flutterWebView.listenForRedirect("mobile://test.com", true);

    flutterWebView.onWebViewDidStartLoading.listen((url) {
      setState(() => _isLoading = true);
    });
    flutterWebView.onWebViewDidLoad.listen((url) {
      setState(() => _isLoading = false);
    });
    flutterWebView.onRedirect.listen((url) {
      flutterWebView.dismiss();
      setState(() => _redirectedToUrl = url);
    });
  }



  void reload() {
    flutterWebView.load(
      "https://google.com",
      headers: {
        "X-SOME-HEADER": "MyCustomHeader",
      },
    );
  }
}

How can I use launchWebViewExample in another class?


Solution 1:

You can write a file with just that function, like:

test.dart

void launchWebView () {
  print("1234");
}

and then import that file like this:

main.dart

import "test.dart";

class _MyHomePageState extends State<MyHomePage> {
   @override
   Widget build(BuildContext context) {
       launchWebView();

It is not really clean, but you can do that. Alternatively you can use a class with a static method like:

class test {
    static void foo() {
        print("1234");
    }
}

and then in your code invoke it like that (after the import):

test.foo();

Solution 2:

Or you can just declare all your functions (helpers) inside a class and pass them as an argument to other class.

//The class which contains your functions
class HelperFunction{

  //Define your method
  void launchWebView () {
    print("1234");
  }

  //Pass that function to a class
  MyHomePage(launchWebView);

}

//The class which receives the function.
class MyHomePage extends StatefulWidget{
  //Retrieve the function and store it to a variable of type Function.
  final Function launchWebView;
  MyHomePage(this.launchWebView);
}

class _MyHomePageState extends State<MyHomePage> {
   @override
   Widget build(BuildContext context) {
     //Access that function in State class using widget keyword.
     widget.launchWebView();
   }
}  

Solution 3:

Use mixin instead of global functions!

Why does this happen?

I'm using it over several different files and would love to create its own file and reference with the _launchwebview function anywhere in my app because there are several lines of code needed in order to make it work.

Underscored methods are private to a given library. Thus, if we define _launchwebview in one file, that function is in a mini-library (Source). So, the method will only be accessible within that file. I explored how to expose private methods across files, but found the solution messy. I thus think the question would be better answered using public functions. The problem is implementing a shared function within different classes rather than simply providing access.

I've chosen to add this solution for this particular question since the method (launching a web view) would be good to have implemented within every appropriate Widget class. Note that extension methods could also work in this case, but Flutter prefers composition over inheritance.

If we want to move a public, i.e. non-underscored method, to a different class without copy-pasting, we could try several outlined approaches. I have detailed the drawbacks of the existing options below.

Hacky solutions

  1. Using the part directive enables us to copy the source of one file with a private implementation and use it in another file. But this is discouraged because the technique increases the binary size, goes against the Effective Dart usage guide, and is clearly a code smell because we are copy-pasting the same code.
  2. Using global functions doesn't follow Object Oriented programming. Global state breaks the Encapsulation principle. OOP is the best paradigm to use within Dart apps because of the way the language is designed (classes). The top answer recommends global functions, however, this should be a last resort given the powerful language capabilities of Dart.
  3. Static functions break encapsulation and the Open-Closed principle. They are hard to integrate with state management solutions because we track the state within instance method contexts. For example, integrating provider and other packages like GetX.
  4. Defining a Function as a property of a class can lead to subtle bugs further down the line. For example, we could assign any function to this property because there is no function signature type checking. Furthermore, this is not a scalable solution with code duplications.

Solution: Mixins

Dart has inbuilt support for optionally adding functions to a class when we want to reduce duplicated code but avoid extending the whole class (Source). The mixin keyword enables this by mixing a class with some specific logic. We can restrict the mixin to a specific subclass with on if needed.

Code example

LaunchWebView.dart

mixin LaunchWebView on StatelessWidget { // you can also constrain the mixin to specific classes using on in this line.
  void launchWebView() {
    // Add web view logic here. We can add variables to the mixin itself as well.
  }
}

Usage

class ExampleClass extends StatelessWidget with LaunchWebView {
  Widget build(BuildContext context) {
    ....
  }

  void testFunction() {
    // We now have access to launchWebView().
    launchWebView();
  }
}

Solution 4:

You can do that in different ways:

1. Global function:

Define your function in a file, say global.dart:

void func() => print('Hello');

To use it in any file, just call:

func();

2. Static function in a class:

Create a class, say Foo and define your function in it:

class Foo {
  static void func() => print('Hello');
}

To use it in any file, just call

Foo.func();

3. Use mixin:

  • If you want to use the function in any class:

    Create a mixin, say Bar:

    mixin Bar {
      void func() => print('Hello');
    }
    

    To use it in a class, just use with keyword followed by mixin.

    class Baz with Bar {
      void main() => func();
    }
    
  • If you want to restrict the mixin from being used with any class:

    class Foo {}
    

    Create a mixin, say Bar which is on Foo.

    mixin Bar on Foo {
      void func() => print('Hello');
    }
    

    To use Bar mixin we need to extend Foo class because this is what it is on.

    class Baz extends Foo with Bar {
      void main() => func();
    }