DEV Community

JR Saucedo
JR Saucedo

Posted on

Retgistro 003 — Flutter desde Cero: Simplificando tu main.dart y Dominando la Inyección de Dependencias

Luego de platicar sobre la planeación y estipular la arquitectura a utilizar, porque como bien sabemos, y si no te lo había contado, aplicar una arquitectura como Clean Architecture puede tener muchas variantes. Al final, más que una estructura rígida, termina siendo una filosofía que podemos adaptar según las necesidades e interpretaciones, siempre cumpliendo con sus principios. Pero bueno, si queremos profundizar más en eso, podemos leer el artículo anterior: Registro 002. Porque ahora sí, en este artículo, comenzaremos a escribir código.

Image description

Inyeccion de dependencias y primeros ajustes.

Como lo platicamos, esta sería nuestra estructura; no cambia mucho a lo normal. Contamos con un archivo main.dart y otros directorios. Pero en este caso, limpiaremos nuestro archivo y le quitaremos unas cuantas responsabilidades que no sean las de inicialización.

Originalmente, nuestro archivo main.dart suele venir así:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // TRY THIS: Try running your application with "flutter run". You'll see
        // the application has a purple toolbar. Then, without quitting the app,
        // try changing the seedColor in the colorScheme below to Colors.green
        // and then invoke "hot reload" (save your changes or press the "hot
        // reload" button in a Flutter-supported IDE, or press "r" if you used
        // the command line to start the app).
        //
        // Notice that the counter didn't reset back to zero; the application
        // state is not lost during the reload. To reset the state, use hot
        // restart instead.
        //
        // This works for code too, not just values: Most code changes can be
        // tested with just a hot reload.
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // TRY THIS: Try changing the color here to a specific color (to
        // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
        // change color while the other colors stay the same.
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          //
          // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
          // action in the IDE, or press "p" in the console), to see the
          // wireframe for each widget.
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Haremos un cambio bastante fuerte, quitando todo lo que no necesitamos que esté aquí y, como mencionamos, solo dejaremos lo necesario para inicializar nuestra app. Llegamos a esto:

import 'package:error_stack/error_stack.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';

import 'package:supabase_flutter/supabase_flutter.dart';

import 'core/app/app.dart';
import 'core/service_locator.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Supabase.initialize(
    url: const String.fromEnvironment(
      'SUPABASE_URL',
    ),
    anonKey: const String.fromEnvironment(
      'SUPABASE_ANON_KEY',
    ),
    realtimeClientOptions: const RealtimeClientOptions(
      logLevel: kDebugMode ? RealtimeLogLevel.debug : RealtimeLogLevel.info,
    ),
  );
  await setupServiceLocator();
  await ErrorStack.init();
  runApp(const MainApp());
}
Enter fullscreen mode Exit fullscreen mode

Encontramos quizás código conocido para todos y otro no tan conocido.

  1. WidgetsFlutterBinding.ensureInitialized(); Con este paso, nos aseguramos de que el Flutter engine esté listo para comenzar nuestras acciones, especialmente nuestra conexión con Supabase.

  2. Inicializamos nuestra conexión con Supabase, trayendo de nuestro environment las variables necesarias. A su vez, le designamos un logLevel a nuestras comunicaciones en tiempo real.

  • Para hacer funcionar String.fromEnvironment() cuando corramos nuestra app y hagamos build, tendremos que agregar nuestras variables como argumentos: --dart-define=SUPABASE_URL=*****, separando un argumento por cada variable de ambiente.

  • Si estás corriendo en debug directamente desde VS Code, estos argumentos extras puedes agregarlos en el launch.json:

Image description

  • Si estas usando Android Studio, entras a la configuración de debugging y agregas los argumentos extras:

Image description

  1. Luego, iniciamos nuestro serviceLocator, que se encargará de la inyección de dependencias.

  2. Tenemos una librería que nos muestra mayor información cuando tenemos errores que no estamos capturando, muy útil: ErrorStack.

  3. Al final, contamos con nuestro clásico runApp.

Service Locator

Luego de ver los detalles en main.dart, podemos pasar al archivo que es un tanto diferente a lo que podríamos estar acostumbrados a ver en nuestros proyectos de Flutter. Para el caso de la inyección de dependencias, utilizaremos la librería get_it. La inicialización de este es muy sencilla, al menos al inicio. Creamos un archivo service_locator.dart en nuestro directorio core.

import 'package:get_it/get_it.dart';

final GetIt sl = GetIt.instance;

Future<void> setupServiceLocator() async {}
Enter fullscreen mode Exit fullscreen mode

Como bien podemos ver, el método setupServiceLocator es el que invocamos en main.dart y, en su caso, la instancia sl es la que llamaremos para acceder a las dependencias. Vamos a configurar una dependencia que es la principal a utilizar, en este caso el cliente de Supabase, aunque en otros proyectos podría ser el cliente de Dio o el cliente HTTP que utilices.

Configuración de dependencias - Supabase

La instancia de Supabase ya la inicializamos en el main.dart, ahora hagámosla accesible a través de nuestro inyector. Creamos un archivo dentro de core/services y lo llamaremos supabase_client.dart. En él colocaremos una clase donde accederemos a las librerías que necesitamos de Supabase.

import 'package:supabase_flutter/supabase_flutter.dart';

class SupabaseNetwork {
  SupabaseNetwork(this._supabase) {
    _supabase = Supabase.instance;
  }
  Supabase _supabase;
  SupabaseClient get client => _supabase.client;
}
Enter fullscreen mode Exit fullscreen mode

Por cuestiones de naming, no pudimos llamar nuestra clase igual que nuestro archivo, pero este archivo va más como ejemplo. Porque si bien podríamos agregarlo directo a nuestro serviceLocator, como bien mencioné, para ejemplificar en nuestro proyecto, estamos haciéndolo así.

Luego de que tenemos nuestra clase, la registramos en nuestro serviceLocator.

import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

import 'services/supabase_client.dart';

final GetIt sl = GetIt.instance;

Future<void> setupServiceLocator() async {
  sl
    //Services
    ..registerFactory<SupabaseClient>(
      () => SupabaseNetwork(Supabase.instance).client,
    );
}
Enter fullscreen mode Exit fullscreen mode

En este caso, lo registramos como factory, para acceder a una nueva instancia con cada get que realicemos de la dependencia. Luego, con otros tipos, usaremos otros tipos de registro para poder compartir el mismo estado de las dependencias según el caso.


Creo que por este artículo ha sido bastante información. En la siguiente entrega abordaremos la creación del features/users, donde veremos la inyección de la dependencia y conexión con data_source, repos, y bloc en sus diferentes ubicaciones, así como también el registro de esas dependencias.






Top comments (0)