Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HELP] Example of event listening inside the screen widget #31

Closed
jlubeck opened this issue Apr 3, 2020 · 3 comments
Closed

[HELP] Example of event listening inside the screen widget #31

jlubeck opened this issue Apr 3, 2020 · 3 comments

Comments

@jlubeck
Copy link

jlubeck commented Apr 3, 2020

Sorry to be bugging so much... can you tell I'm new to flutter?

I was able to implement the middleware to observer every time a screen gets pushed or popped. But I don't understand how to use that to actually perform something on the screen itself. For instance, I want to refresh data from an API every time a screen is either pushed or popped.

I have this in my Middleware

class MiddleWare {
  static observer(Routing routing) {
    print(routing.current);
  }
}

And I do get the name of the screen when I expect it to appear. But how do I call the method on that screen?

Thank you!

@jonataslaw
Copy link
Owner

jonataslaw commented Apr 4, 2020

If you're new, maybe I'm introducing you to unnecessary complexity, but it might be good for you to learn some basics.

Let's say you want to do a search for the API reactively, using Bloc, and you don't want to use any extra packages for that because you want complete control of what you're doing.
At a minimum you need to transform the class Bloc you are going to use into a singleton, so you can access it from anywhere.
The sample code contains a disposable singleton model that replaces the package for example from the provider/get_it. Instead of adding a new dependency, and it will always have the same instance, it will only be placed in memory when you call, and you can give dispose to save memory at any time.
In any other language I prefer DI to use singletons, but in Dart, they are not a problem with tests, and can be disposable, which is a great advantage of using them here.

Example:

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: Get.key,
      onGenerateRoute: Router.generateRoute,
      navigatorObservers: [GetObserver(MiddleWare.observer)],
      home: First(),
    );
  }
}

class Router {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    switch (settings.name) {
      case '/':
        return GetRoute(
          page: First(),
          settings: settings,
        );
      case '/second':
        return GetRoute(
            settings: settings, page: Second(), transition: Transition.fade);
      case '/third':
        return GetRoute(
            settings: settings,
            page: Third(),
            transition: Transition.cupertino);
      default:
        return GetRoute(
            settings: settings,
            transition: Transition.fade,
            page: Scaffold(
              body:
                  Center(child: Text('No route defined for ${settings.name}')),
            ));
    }
  }
}

class First extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            Get.snackbar("hi", "i am a modern snackbar");
          },
        ),
        title: Text('First Route'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Open route'),
          onPressed: () {
            Get.toNamed("/second");
          },
        ),
      ),
    );
  }
}

class Second extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            Get.snackbar("hi", "i am a modern snackbar");
          },
        ),
        title: Text('second Route'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Open route'),
          onPressed: () {
            Get.toNamed("/third");
          },
        ),
      ),
    );
  }
}

class Third extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () => Get.back(),
        child: Icon(Icons.arrow_back),
      ),
      appBar: AppBar(
        title: Text("Third Route"),
      ),
      body: StreamBuilder<List<Job>>(
          stream: JobBloc().jon,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              List<Job> data = snapshot.data;
              return ListView.builder(
                  itemCount: data.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(data[index].company,
                          style: TextStyle(
                            fontWeight: FontWeight.w500,
                            fontSize: 20,
                          )),
                      subtitle: Text(data[index].description),
                    );
                  });
            } else if (snapshot.hasError) {
              return Text("${snapshot.error}");
            }
            return Center(child: CircularProgressIndicator());
          }),
    );
  }
}

class MiddleWare {
  static observer(Routing routing) {
    if (routing.current == "/third") {
      print(routing.current);
      // Fill your class with data
      JobBloc().fetchData();
    } else if (routing.previous == "/third") {
      JobBloc().dispose();
    }
  }
}

class JobBloc {
  //create private instance
  static JobBloc _instance;
  // create a factory than give always same instance
  factory JobBloc() {
    if (_instance == null) _instance = JobBloc._();
    return _instance;
  }
  JobBloc._();

  //create a Stream
  StreamController<List<Job>> _job = StreamController<List<Job>>.broadcast();

  fetchData() async {
    // fetch data from api
    List<Job> data = await Api.result();
    //add result on stream
    _job.add(data);
  }

  // expose the stream
  get jon => _job.stream;

  dispose() {
    //close the stream
    _job.close();
    //Assign your class to null to reset your instance and have GB clear your class from memory to save resources.
    _instance = null;
  }
}

class Api {
  static Future<List<Job>> result() async {
    //fetch data from internet
    http.Response response =
        await http.get("https://mock-json-service.glitch.me/");
    if (response.statusCode == 200) {
      // Transform the response string into an object
      List jsonResponse = json.decode(response.body);
      //Iterate each object in a list and return it
      return jsonResponse.map((job) => Job.fromJson(job)).toList();
    } else {
      // show one snackbar on error case
      Get.snackbar("Error", "Your API denied this request");
      return [];
    }
  }
}

// create a Model class. I use https://app.quicktype.io/ to autogenerate it
class Job {
  final int id;
  final String position;
  final String company;
  final String description;

  Job({this.id, this.position, this.company, this.description});

  factory Job.fromJson(Map<String, dynamic> json) {
    return Job(
      id: json['id'],
      position: json['position'],
      company: json['company'],
      description: json['description'],
    );
  }
}

Note: We don't need any Stateful widgets.
Every time you leave the screen, your block class will be removed from memory.
If the connection fails, a snackbar will be displayed to alert the user (this is one of the things that is only possible with Get, it would be impossible to call a snackbar of the class API using the default framework).
If you have 10 screens, and you want the data to be updated when the user returns from a screen, this is also possible.
Note2: I am not recommending that you do all your projects like this, it is just an example of possibilities that the Framework gives you, and a way to make your application practically Stateless, which can be advantageous in some projects, but bad in others. If you have a TextEditController, for example, your class must be StatefulWidget, never StatelessWidget.

@jlubeck
Copy link
Author

jlubeck commented Apr 15, 2020

Hey @jonataslaw thank you a lot for this, I got caught up with other projects, but I'm trying this now.
By any chance Is there a place where you hang out to chat about flutter? I wouldn't want to keep making tickets for simple stuff that is outside the scope of the library..

@jlubeck
Copy link
Author

jlubeck commented Apr 15, 2020

This worked like a charm. Thank you for the input Jonny

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants