Arquitectura de Aplicaciones

1

Pasos para aprender Flutter Avanzado

2

¿Qué es una Arquitectura de software?

3

Tipos de Arquitecturas para Flutter

4

Arquitectura BLoC en Flutter

5

BLoC + Clean Architecture en Flutter

6

Estructurando nuestro proyecto utilizando la Arquitectura BLoC

7

Haciendo BLoC Singleton en Flutter

8

Aplicando Providers al Proyecto

Firebase y Flutter

9

Integrando Firebase Authentication, Cloud Firestore y Firebase Storage al proyecto

10

Integrando Firebase a Flutter para Android

11

Integrando Firebase a Flutter para iOS

12

Creación de Pantalla de Login con Flutter

13

Creando botones reutilizables en Flutter

14

Autenticación de Firebase con Google

15

Implementando Firebase Authentication en BLoC Pattern

16

Streams en Flutter

17

Manejando una sesión con Firebase Authentication y Flutter

18

Implementando Google SignOut en BLoC

19

Implementando Google SignOut en View

20

Monitoreando y validando la conexión al Sign con Google

21

Mostrando los datos de usuario de Google en la interfaz en Flutter

Cloud Firestore de Firebase en Flutter

22

¿Qué es Cloud Firestore de Firebase?

23

Analizando un modelo de datos no relacional

24

Creando un Modelo de datos en Cloud Firestore

25

Enviando datos a Cloud Firestore

26

Creando un Widget gradiente personalizado

27

Manejo de Desbordamiendo de Texto de Widget Text

28

Botón de Back en un Appbar en Flutter

29

Navegación entre pantallas en Flutter

30

Widget Text Appbar personalizado en Flutter

31

Widget TextField personalizado en Flutter

32

Creando una Safe Area para una interfaz que tiene un AppBar

33

Widget TextField con iconos en Flutter

34

Retocando el CardView

35

Mostrando imágenes en un CardView

36

Creando un botón de Submit en Flutter

37

Envío de datos de un fórmulario en Flutter

38

Subiendo datos a Firestore de Firebase

39

Formularios en Flutter

Acceso al Hardware con Flutter

40

Acceso a la cámara en Flutter

41

Librerías de acceso a Hardware en Flutter

Firebase Storage en Flutter

42

Qué es y cómo funciona Firebase Storage en Flutter

43

Subiendo una imagen a Firebase Storage desde Flutter

Querys avanzados en Cloud Firestore de Firebase en Flutter

44

Manejo de imágenes en Cloud Firestore

45

Cloud Firestore insertando referencias y arrays en la base de datos

46

Descargar imágenes de Firebase Storage y mostrarlas en Flutter

47

Procesando datos con BLoC Pattern

48

Trayendo datos de Cloud Firestore

49

Persistiendo datos de un usuario logueado

50

Aplicando Filtros en Cloud Firestore

51

Construyendo los Places en la pantalla de Home

52

Mostrando los Places en la pantalla de Home

53

Actualizando datos en tiempo real

54

Manejando la lógica de likes, como botón toggle.

55

Insertando y obteniendo referencias en datos de Firestore.

56

Usando el caché para cargar imágenes más rápido

57

StreamController, sink, add y StreamBuilder

Conclusiones

58

Conclusiones

Curso Avanzado de Flutter

Curso Avanzado de Flutter

Camila Barajas Salej

Camila Barajas Salej

StreamController, sink, add y StreamBuilder

57/58

Lectura

Aprovecharemos lo aprendido en nuestras primeras clases sobre Streams para crear un Stream personalizado.

Durante el curso estuvimos utilizando los Streams que nos proveía Firebase, pero es momento de crear un Stream propio. Esto surge por la necesidad de implementar la muestra de los detalles de cada Place.

En la última vista de nuestra aplicación mostrabamos un texto genérico refiriéndonos a las Bahamas.
Captura de pantalla 2019-05-27 a la(s) 3.58.48 p. m..png

Por la estructura que tiene nuestra aplicación en la ventana HomeTrips() no tenemos accesibles los datos ni de la lista de lugares para la descripción ni de la descripción para la lista, ambos se encuentran en archivos independientes.

class HomeTrips extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Stack(
      children: <Widget>[
        ListView(
          children: <Widget>[
            DescriptionPlace("Bahamas", 4, descriptionDummy),
            ReviewList()

          ],
        ),
        HeaderAppBar()
      ],
    );
  }

}

Lo que haremos con la definición del StreamController es así como con Firebase poníamos alerta el Widget StreamBuilder y este siempre estaba pendiente de reflejar los cambios en la interfaz de usuario, ahora necesitamos reflejar siempre los datos del Place seleccionado en DescriptionPlace de tal forma que no tengamos que pasar los datos como parámetro sino que este automáticamente los refleje.

1. Comenzaremos en el archivo bloc_user.dart

  • Primero definiremos el tipo de elemento que será escuchado por el StreamController, para nuestro caso es un Place seleccionado, por eso escribiremos el siguiente código:
StreamController placeSelectedStreamController =  StreamController();
  • Luego necesitaremos convertir este StreamController en algo que pueda leer el StreamBuilder en su parámetro stream por eso lo transformaremos de esta forma:
Stream get placeSelectedStream => placeSelectedStreamController.stream;
  • Ahora dejaremos disponible el Stream para que se le pueda insertar el elemento Place, esto lo hacemos através del método sink que solo se puede implementar al StreamController.
StreamSink get placeSelectedSink =>  placeSelectedStreamController.sink;

Una vez que todo está declarado en el archivo Bloc que mantiene una persistencia Global en la aplicación, ya podemos ingresar el objeto Place cuando lo necesitemos.

Mira el archivo completo aquí

2. Vamos al archivo card_image_list.dart para añadir la funcionalidad de al dar click en la Foto podamos mostrar los detalles

  • Para lograr que la Card sea clickeable utilizaremos un Widget nuevo GestureDetector(). Este widget es muy útil pues básicamente hace clickeable cualquier área y además nos provee de un método onTap que nos permitirá controlar el comportamiento.
    Nuestra lista ser verá envuelta por este widget de esta forma:
return ListView(
      padding: EdgeInsets.all(25.0),
      scrollDirection: Axis.horizontal,
      children: places.map((place){
        return GestureDetector(
          onTap: (){
            print("CLICK PLACE: ${place.name}");
            userBloc.placeSelectedSink.add(place);
          },
          child: CardImageWithFabIcon(
            pathImage: place.urlImage,
            width: 300.0,
            height: 250.0,
            left: 20.0,
            iconData: place.liked?iconDataLiked:iconDataLike,
            onPressedFabIcon: (){
              setLiked(place);
            },
            internet: true,
          ),
        );
      }).toList(),
);
  • Ahora es importante no dejar independiente el evento onPressed del botón like, pues en la descripción también estaremos mostrando la cantidad de likes que tiene el Place, así que en el método setLiked implementaremos la lógica de incremento o decremento de likes del place, para que este dato también sea persistente en la descripción.
void setLiked(Place place){
      setState(() {
        place.liked = !place.liked;
        userBloc.likePlace(place, widget.user.uid);
        place.likes = place.liked?place.likes+1:place.likes-1;
        userBloc.placeSelectedSink.add(place);
      });
}

Mira el archivo completo aquí

3. Por último en el archivo description_place.dart implementaremos el StreamBuilder que estará alerta para mostrar los datos del Place una vez que se seleccione.

  • Implementemos una instancia de UserBloc en el método build así:
UserBloc userBloc = BlocProvider.of(context);
  • Ahora agreguemos el widget StreamBuilder en el return:
return StreamBuilder(
      stream: userBloc.placeSelectedStream,
      builder: (BuildContext context, AsyncSnapshot snapshot){
          
      });
  • Validaremos el estado de los datos:
if (snapshot.hasData) {
    Place place = snapshot.data;
    return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          titleStars(place),
          descriptionWidget(place.description),
          ButtonPurple(buttonText: "Navigate", onPressed: (){})
        ],
    );
}else {
    return Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container (
            margin: EdgeInsets.only(
                top: 400.0,
                left: 20.0,
                right: 20.0
            ),
            child: Text(
              "Selecciona un lugar",
              style: TextStyle(
                  fontFamily: "Lato",
                  fontSize: 30.0,
                  fontWeight: FontWeight.w900
              ),
              textAlign: TextAlign.left,
            ),
          )
        ],
    );
}

Notaste que los Widgets ahora están en forma de funciones titleStars y descriptionWidget:

Widget titleStars(Place place){
    return Row (
      children: [
        Container (
          margin: EdgeInsets.only(
              top: 350.0,
              left: 20.0,
              right: 20.0
          ),
          child: Text(
            place.name,
            style: TextStyle(
                fontFamily: "Lato",
                fontSize: 30.0,
                fontWeight: FontWeight.w900
            ),
            textAlign: TextAlign.left,
          ),
        ),
        Container (
          margin: EdgeInsets.only(
              top: 370.0,
          ),
          child: Text(
            "Hearts: ${place.likes}",
            style: TextStyle(
                fontFamily: "Lato",
                fontSize: 18.0,
                fontWeight: FontWeight.w900,
                color: Colors.amber
            ),
            textAlign: TextAlign.left,
          ),
        ),
      ],
    );
  }

Widget descriptionWidget(String descriptionPlace){
    return Container(
      margin: new EdgeInsets.only(
          top: 20.0,
          left: 20.0,
          right: 20.0
      ),
      child: new Text(
        descriptionPlace,
        style: const TextStyle(
            fontFamily: "Lato",
            fontSize: 16.0,
            fontWeight: FontWeight.bold,
            color: Color(0xFF56575a)
        ),
      ),
    );
  }

Mira el archivo completo aquí

Mira el proyecto completo aquí

Aportes 13

Preguntas 0

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Agreguen esta línea a su Bloc para evitar leaks de memoria:

  @override
  void dispose() {
    _placeSelectedController.close();
  }

Al implementar este Stream personalizado pude evidenciar fácilmente la utilidad del patrón BLoC!

Vistas totalmente distintas en un nivel de jerarquía distinto pueden interactuar con la lógica de negocio a través de los Streams. Todo esto gracias al BlocProvider, el cual se encuentra ubicado en lo más alto del nivel jerárquico y que permite tener acceso al UserBloc (En donde se definen los streams).

Para este caso, los Places (Cards) de la CardImageList en su evento onTap:

onTap: (){
    print("CLICK PLACE: ${place.name}");
    userBloc.placeSelectedSink.add(place);
},

Utilizan la entrada del Stream: el SINK para añadir el objeto Place (que servirá como referencia para saber que lugar se clickeó de la lista de cards)

De esta manera todos los StreamBuilder que estén a la escucha del stream identifiquen este cambio.

Es por esto que en el cuerpo de la pantalla HomeTrips, específicamente en DescriptionPlace, se construye el código del StreamBuilder que permanece escuchando los cambios

AQUI SE PUEDE NOTAR EN LA LINEA

 stream: userBloc.placeSelectedStream,

CODIGO QUE PERTENECE A DESCRITION PLACE


return StreamBuilder<Place>(
      stream: userBloc.placeSelectedStream,
      builder: (BuildContext context, AsyncSnapshot<Place> snapshot){
        if (snapshot.hasData) {
          print("PLACE SELECTED: ${snapshot.data.name}");
          Place place = snapshot.data;
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              titleStars(place),
              descriptionWidget(place.description),
              ButtonPurple(buttonText: "Navigate", onPressed: (){})
            ],
          );
        }else {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Container (
                margin: EdgeInsets.only(
                    top: 400.0,
                    left: 20.0,
                    right: 20.0
                ),

                child: Text(
                  "Selecciona un lugar",
                  style: TextStyle(
                      fontFamily: "Lato",
                      fontSize: 30.0,
                      fontWeight: FontWeight.w900
                  ),
                  textAlign: TextAlign.left,
                ),

              )

            ],
          );
        }
      },
    );

En conclusión, comunicamos 2 vistas pasando cualquier dato, sin importar el nivel jerárquico que tengan en el árbol de Widgets.

Que paso con PlaceBloc? Solo se puede implentar un bloc?

Si les aparece el siguiente error:
Stream has already been listened to.
Utilicen esto en el bloc y listo:
StreamController<Place> placeSelectedStreamController = StreamController<Place>.broadcast();

Amigos, hasta aqui ya casi me doy por vencido, solo una pregunta, como puedo hacer esta publicacion un archivo de produccion uno pára Andrid y otro para IOs, espero me puedan ayudar. Saludos.

Como se nota que se les acabaron los recursos para grabar video 😭

El like en en los places que muestra en Profile no esta programado.

Hola que tal, una pregunta amigos, ya que no pose una mac para poder probar al mismo tiempo la versin en iOS, puedo hacer la app primero en androidy luego probar su avance en una Mac prestada?. o Saben de alguna forma de desarrollar en estos casos. Gracias

Genial ahora solo queda tratar de embellecer mas la app y quedará muy bien.

Hola,
Esta implementación no actualiza automáticamente la descripción del Place, cuando surge algún cambio en la base de datos ocasionado por algún otro usuario (dar like, modificar descripcion, eliminación, etc). Es necesario volver a hacer click en el place para actualizarlo, ya que el sink toma valor actualizado solo cuando se da click en un place.
Pensaba solucionarlo poniendo a disposición los places en una jerarquía mayor(HomeTrips) con el streamBuider. Luego pasar los places a CardImageList y DescriptionPlace. En DescriptionPlace, Buscar en la lista de places recibida el place a mostrar con datos actualizados, usando el ID del PlaceSeleccionado, de modo tal que el place mostrado siempre se actualizará cuando haya un cambio de BD o cuando el usuario seleccione otro place. También se podría extraer el stremBuilder que hay en descriptionPlace al nivel del widget HomeTrips, para buscar ahí mismo el place a mostrar en la descripción y luego pasarlo a DescriptionPlace.
Sin embargo, veo muy engorroso hacerlo. Habrá una manera más elegante de hacerlo?? De repente algún método de la clase Stream o StreamSink que nos pueda ayudar??

Slds.

Me salio este error, Couldn’t read file LocalFile: (Dirección de algo de mi pc) Please verify that this file has read permission and try again. alguien sabe que esta pasando?

Está genial el que enseñen a usar Stream

Oh wait til’ I do what I do,
Hit you with that ddu-du ddu-du du