MVP: Qué es y su importancia en la creación de productos

Los prototipos a menudo influyen en un MVP y los dos trabajan juntos para crear un producto final exitoso. Un MVP (del inglés Minimum Viable Product) es una forma mínima de su producto completo que se prueba en el mercado.
Este enfoque de desarrollo le permite aprender cómo reaccionarán sus usuarios a su producto antes de que desperdicie una gran cantidad de dinero y recursos construyendo algo que nadie quiere o necesita. Si bien los prototipos resuelven problemas durante las primeras etapas de desarrollo, el proceso iterativo de un MVP está diseñado para identificar los puntos débiles de los usuarios cuando el producto se prueba realmente en el mercado.
El riesgo de desarrollar más (o menos) de lo que necesita es la razón por la que es tan importante validar los supuestos de su producto con un MVP. Comenzando con una característica central, aprender cómo reaccionan los usuarios a esa característica y construir de acuerdo con los comentarios de los usuarios es esencial para determinar la cantidad adecuada de funcionalidad que su producto necesita para adquirir y retener usuarios. Con el tiempo, los aprendizajes que provienen de un MVP definen la hoja de ruta de su producto y guían la evolución de su aplicación.
Un MVP es una versión de un producto que incluye solo las características que necesita para ser comercializable. Con el proceso MVP, se puede verificar lo siguiente:
  • Viabilidad del producto
  • Supuestos del producto
  • Usabilidad
  • Demanda del mercado
Producto mínimo viable (MVP): qué es, características, ventajas, ejemplos y cómo se desarrolla
Los MVP brindan valor inmediato al tiempo que minimizan los costos de desarrollo. En última instancia, un MVP le permite crear un producto con características mínimas y desarrollarlo de forma iterativa para crear un producto mejor y más pulido al tiempo que aprovecha la inteligencia del usuario para tomar las mejores decisiones posibles. Con cada versión de lanzamiento, el producto evoluciona para maximizar el ROI y avanzar hacia una aplicación completamente madura.
Todas las aplicaciones más exitosas de la actualidad comenzaron como algo mucho más simple de lo que son hoy. Las aplicaciones como Uber, Instagram y Spotify, por ejemplo, son todas aplicaciones maduras; son el resultado de años de desarrollo y grandes cantidades de capital. Desarrollar una aplicación de alcance similar requiere mucho tiempo y una gran inversión.
Aquí es donde entra en juego un MVP, ya que proporciona valor inmediato, rápidamente, al tiempo que minimiza los costos de desarrollo y revela la dirección más adecuada para un mayor desarrollo.
Lo que implica un MVP puede ser muy subjetivo, difiriendo de una organización a otra en función de las necesidades comerciales, la industria y la competencia.
Dado que un MVP implica salir al mercado con características y funcionalidades centrales, le permite comenzar a construir una base de usuarios para obtener información sobre lo que funciona y lo que no. Esta es información vital, ya que permite a los equipos de productos utilizar datos para tomar decisiones sobre futuras iteraciones del producto, incluidas las otras funciones que se agregarán, los aspectos que aumentarán las ventas y exactamente dónde asignar el presupuesto.
No será solo cuestión de desarrollar la mejor versión posible de tu aplicación, sino de conocer a profundidad a tu usuario y satisfacer de la mejor manera su necesidad.

Monetización de apps con Flutter

La monetización de apps no es un mito, es una realidad…

Antes de lanzar tu app habrás definido bien todas sus funcionalidades, tu estrategia de adquisición de usuarios y cómo retenerlos. En este punto, toca plantearse la siguiente fase: cómo ganar dinero, es decir, transformar el engagement de tus usuarios en ingresos para tu negocio. Existen distintos tipos de monetización para apps y tendrás que determinar la estrategia que funcione mejor para alcanzar tus objetivos de rentabilidad.

En FlutterLab te enseñamos como hacerlo 👋

No hay descripción alternativa para esta imagen

Ahora que ya conoces cómo monetizar tu aplicación, ¿Qué esperas para hacerlo?

Navigator 2.0 en Flutter

 

La navegación es parte esencial de cualquier software, tanto web como móvil, ya que este nos permite realizar el desplazamiento entre enlaces de una página a otra.

Para entender mejor la apuesta que Flutter está realizando al cambiar el concepto de su Navigator a Navigator 2.0, es necesario entrar en contexto sobre estas dos API’s:

Understanding Flutter Navigator 2.0 | Codemagic Blog

 

Navigator

Si estás usando Navigator significa que estás familiarizado con:

 

  • Navigator: un widget que administra una pila de objetos de rutas.
  • Route: un objeto administrado por un Navigator que representa una pantalla, normalmente implementado por clases como MaterialPageRoute.

 

Uno de los principales problemas con el Navigator actual es que su implementación se basa en una programación imperativa (vamos definiendo el stack de la navegación a medida que el desarrollo va avanzando). Lo anterior es completamente opuesto a la programación declarativa y reactiva con el cual está construido Flutter.

Otro de los grandes problemas que tiene la navegación actual son la cantidad de parámetros en los que podemos declarar nuestra navegación dentro del Widget MaterialApp, ya que en la actualidad Flutter nos permite implementar la navegación a través de los siguientes parámetros:

  • Routes
  • OnGenerateRoute
  • OnGenerateInitialRoutes
  • OnUnknownRoute

Lo anterior hace que la implementación de la navegación en un inicio sea un tanto confusa, sin contar que existe otra cantidad de formas de poder declarar nuestra navegación y aplicarla imperativamente.

Por ejemplo:

Opción 1

  • MaterialApp(
      debugShowCheckedModeBanner: false,
      title: ExampleFlutterLab,
      theme: ThemeData(
        primaryColor: Colors.white,
        scaffoldBackgroundColor: AppColors.backgroundGrey,
        textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme),
      ),
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: L10n.all,
      navigatorKey: AppNavigator.navigatorKey,
      onGenerateRoute: Routes.generateRoute,
    ); 
  • class Routes {
      static Route<dynamic> generateRoute(RouteSettings settings) {

        switch (settings.name) {
          case ‘/’: return MaterialPageRoute(builder: (_) => const Login());
          case ‘/Home: return MaterialPageRoute(builder: (_) => const Home());
          default: return MaterialPageRoute(builder: (_) => const Login());
        }
      }
    }
  • Material(
        elevation: 4,
      borderRadius: BorderRadius.circular(3),
      child: InkWell(
        onTap: () => Navigator.of(context).pushNamed(«/home»),
        child: const Text(«let’s go home»),
      ),
    );

(Se agrego en el parámetro OnGenerateRoute una static Function que nos retorne Route<dynamic> y que esta nos agregara en la pila de navegación la nueva vista, en este caso Home())

Opción 2

  • Material(
      elevation: 4,
      borderRadius: BorderRadius.circular(3),
      child: InkWell(
        onTap: () => Navigator.of(context).push(
          new MaterialPageRoute(
            builder: (context) => home(
              user: User(),
            ),
          ),
        ),
        child: const Text(«let’s go home»),
      ),
    ),

(Imperativamente se declara la función que nos permitirá realizar la navegación a la siguiente vista en este caso Home() )

 

Navigator 2.0

Esta API nos permite un control más preciso sobre la pila de navegación, logrando analizar las rutas y eliminar páginas debajo de la actual.

Se agregan nuevas clases al marco, para hacer que las pantallas de la aplicación sean una función de estado (muy similar a lo que se implementa en URL web) entre las que encontramos las siguientes.

  • Page: objeto inmutable que nos permite configurar los Stack en la pila de navegación.
  • Router: es el encargado de abrir y cerrar páginas de una aplicación.

Este widget escucha la información de enrutamiento del sistema operativo, analiza los parámetros de la ruta y luego convierte esos datos en un widget que posteriormente se agrega a la pila de navegación.

  • RouteInformationParser: Cuando el enrutador obtiene una nueva información de ruta de RouteInformationProvider, el Router usa este delegado para analizar la información de ruta y producir una configuración que será utilizada por RouterDelegate que eventualmente procederá a construir el widget de Router.
  • RouterDelegate: Un delegado que utiliza el widget de Router para crear y configurar un widget de navegación.
  • BackButtonDispatcher: Informa en un Router cuando el usuario toca el botón atrás en plataformas que admiten botones atrás (como Android).

 

Por ejemplo:

main.dart

import ‘package:example_flutter_lab/page/home/home.dart’;
import ‘package:example_flutter_lab/page/login/login.dart’;
import ‘package:flutter/material.dart’;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static late String nameUser;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: ‘ExampleFlutterLab’,
      theme: ThemeData(
          primaryColor: Colors.white, scaffoldBackgroundColor: Colors.white),
      home: Navigator(
        pages: [
          MaterialPage(
            child: Login(
              handledOnTap: handledOnTap,
            ),
            key: const ValueKey(«login»),
          ),
          if (nameUser != null)
            const MaterialPage(
              child: Home(),
              key: ValueKey(«home»),
            ),
        ],
        onPopPage: (route, result) => route.didPop(result),
      ),
    );
  }

  handledOnTap(String nameUserParams) => setState(() {
        nameUser = nameUserParams;
      });
}

login.dart

import ‘package:flutter/material.dart’;

class Login extends StatefulWidget {
  final Function(String) handledOnTap;

  const Login({Key? key, required this.handledOnTap}) : super(key: key);

  @override
  _LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Material(
          elevation: 4,
          borderRadius: BorderRadius.circular(3),
          child: InkWell(
            onTap: () => widget.handledOnTap(«user»),
            child: const Text(«let’s go home»),
          ),
        )
      ],
    );
  }
}

¡Navigator 2.0, un mundo fascinante a la espera de la acción!