0
votes

I am working on an app in Flutter and I'm pretty new to it/Dart. I already created the login, signup etc and everything works perfectly fine. Now I want to create a "Login-Wall" Template for every View that needs the user to be logged in. If the user is not logged in, he should be returned to the LoginView, if the api-call is still loading, it should not show anything but a loading screen called LoadingView(). I started by creating a Stateful Widget called AuthorizedLayout:

class AuthorizedLayout extends StatefulWidget {
  final Widget view;

  AuthorizedLayout({this.view});

  _AuthorizedLayoutState createState() => new _AuthorizedLayoutState();
}

The state utilizes a Future Builder as follows:

  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: futureToken,
      builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return NoConnectionView();
          case ConnectionState.active:
          case ConnectionState.waiting:
            return LoadingView();
          case ConnectionState.done:
            if(snapshot.data != null) {
              print("User Data loaded");
              return widget.view;
            } else
              return LoginView();
        }
      },
    );
  }

As you can see, it should load the userdata, and when it's finished it should return the view. The futureToken represents the Future that will return the User-Object from the server after an api-request. In any other case it should show the Loading/Error/Login Page.

I'm calling it like this:

  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      body: AuthorizedLayout(
        view: DashboardView(),
      ),
    );
  }

In the Build method of the Dashboard view I have a "print('Dashboard View');". The problem I have is that in the output the 'Dashboard View' is printed before the 'User Data Loaded'. That means I can't access the loaded user data in that view. This means that this solution does not work the way I intended it to.

Now for my question: Is there any way I can build this "Login-Wall" and pass the user data to every view that is inside the login wall? I hope the code I posted explains the idea I'm trying to go for.

2

2 Answers

0
votes

Is there any way I can build this "Login-Wall" and pass the user data to every view that is inside the login wall?

Absolutely! At a basic level, you're talking about state management. Once a user logs into your app, you want to store that user data so that it's accessible to any widget within the widget tree.

State management in Flutter is a hotly-debated topic and while there are a ton of options, there is no defacto state management technique that fits every app. That said, I'd start simple. One of the simplest and most popular options is the scoped_model package.

You can read all of the details here, but the gist is that it provides utilities to pass a data model from a parent widget to its descendants.

First, install the package.

Second, you'll want to create a model that can hold the user data that you want to be accessible to any widget in the tree. Here's a trivial example of what that might look like:

// user_model.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

class UserModel extends Model {
  dynamic _userData;

  void setUserData(dynamic userData) {
    _userData = userData;
  }

  String getFirstName() {
    return _userData['firstName'];
  }

  static UserModel of(BuildContext context) =>
      ScopedModel.of<UserModel>(context);
}

Next, we'll need to make an instance of this UserModel available to all widgets. A contrived way of doing this would be to wrap your entire app in a ScopedModel. Example below:

// main.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'login_view.dart';
import 'user_model.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ScopedModel<UserModel>(
      model: UserModel(),
      child: MaterialApp(
        theme: ThemeData.light(),
        home: LoginView(),
      ),
    );
  }
}

In the above code, we're wrapping our entire instance of MaterialApp in a ScopedModel<UserModel>, which will give every widget in the application access to the User model.

In your login code, you could then do something like the following when your login button is pressed:

onPressed() async {
    // authenticate your user...
    var userData = await someApiCall();

    // set the user data in our model
    UserModel.of(context).setUserData(userData);

    // go to the dashboard
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => DashboardView(),
        ),
    );
}

Last but not least, you can then access that user data through the UserModel like so:

// dashboard_view.dart
import 'package:flutter/material.dart';
import 'package:scoped_model_example/user_model.dart';

class DashboardView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Center(
          child: Text(
            UserModel.of(context).getFirstName(),
          ),
        ),
      ],
    );
  }
}

Check out the docs on scoped_model for more details. If you need something more advanced, there are a number of other state management patterns in Flutter such as BloC, Redux, Mobx, Provider and more.

0
votes

So I just got what was happening. I was passing the already-built widget to the AuthorizedView. What I actually had to pass was a Builder instead of a Widget.

class AuthorizedLayout extends StatefulWidget {
  final Builder viewBuilder;

  AuthorizedLayout({this.viewBuilder});

  _AuthorizedLayoutState createState() => new _AuthorizedLayoutState();
}

Calling it like this:

  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      body: AuthorizedLayout(
        viewBuilder: Builder(builder: (context) => DashboardLayout()),
      ),
    );
  }

Note that I recalled the final variable to viewBuilder instead of view, compared to the example above.

This will actually build the widget AFTER the userdata is loaded.