Cómo logré una experiencia de Gamification con Flutter en 4 días

Nia Cubilla
5 min readApr 18, 2021

Anteriormente he escrito varios posts sobre todo el proceso de desarrollo de mi app Ideas de Negocios Rentables y cómo logré alcanzar 100k descargas en el Play Store con la idea de compartir todo lo que voy aprendiendo y devolver algo a la gran comunidad que tanto me ha ayudado.

Pero algo que no había podido lograr, o mejor dicho, no sabía cómo mejorar era el tema de la retención de usuarios.

Según diversas fuentes en internet, una retención sana está entre el 30% y el 60% en el D1 (Día 1, es decir, 24 horas después de la instalación), sin embargo mi aplicación solo ha mantenido un 14% en D1. Muy por debajo del mínimo recomendado.

Hay muchas maneras de mejorar la tasa de retención que no voy a repetir aquí debido a que ya hay mucha información al respecto en la web. Para mi app opté por incluir una experiencia de gamification, debido a que siento va muy acorde a la naturaleza del mismo app.

Honestamente antes de empezar no tenía ni idea de qué o cómo lo haría, pensé que me tomaría semanas si no es que meses desarrollar todo lo necesario, por lo que grata fue mi sorpresa al completar todo el código, incluso los gráficos en solo 4 días.

Aclarando ideas

Lo primero que hice fue crear una lista sobre las tareas en las que debía trabajar. Quedó de la siguiente manera:

  1. Definir acciones/logros en el app.
  2. Diseñar pantalla de logros.
  3. Diseñar iconos de las insignias.
  4. Escribir mensajes descriptivos de cada insignia.
  5. Programar.

¿Sencillo verdad?

Luego de meditar (fuera de la computadora) pude sentarme y crear una lista de acciones o triggers que dispararían la obtención de cada insignia.

Seguro que hay mucha teoría, estudios y análisis sobre gamification con procesos, hitos, terminología y demás, pero no quise que esta actualización me tomara meses por lo que me propuse trabajar solo en lo que pudiese crear a partir de mi app sin que tomase mucho tiempo.

Al momento de iniciar pensaba en todo esto más como una prueba de concepto que un gran proyecto y en realidad todo con este app ha sido así: pequeñas iteraciones y experimentos “a ver a qué pasa”.

Una vez que tuve mi lista de tareas, cada cosa se fue resolviendo a medida que avanzaba. La clave fue usar la solución más simple a lo que iba necesitando.

Comparto este mensaje porque a veces queremos ir directo al código y lo mejor es aclarar nuestras ideas y pensar las cosas antes.

Ahora si viene lo chido…

Guardando logros del usuario

Actualmente uso Hive para guardar ciertas preferencias de usuario como idioma, modo nocturno y favoritos ya que es mucho más rápido y eficiente que SharedPreferences, así que decidí usar este mismo paquete para crear un “box” (Como llaman en Hive a su base de datos no-relacional) donde se guarden todos logros.

Cabe mencionar que en todos mis proyectos creo una clase “Common” con todas las funciones comunes a usar dentro del app.

Por ejemplo, para saber si el usuario tiene el modo nocturno activado llamo a Common.getNightMode lo que me devuelve true o false según el caso. Para guardar esta misma preferencia llamo a Common.saveNightMode y le paso un parámetro true o false como se puede ver en el siguiente snippet:

class Common {  static void saveNightMode(bool value) {
var box = Hive.box('myBox');
box.put('nightMode', value);
}
static bool getNightMode() {
var box = Hive.box('myBox');
return box.get('nightMode');
}
...}

El código anterior ya nos da una idea para definir nuestro box donde podremos ir guardando y leyendo los logros alcanzados por el usuario, pero para ponerlo más claro supongamos que queremos almacenar todos los logros en un box llamado achievements:

class Common {...static void saveAchievement(String name, bool value) {
var box = Hive.box('achievements');
box.put(name, value);
}
static bool getAchievement(name) {
var box = Hive.box('achievements');
return box.get(name);
}
}

Luego para guardar cada logro solo se tendría que llamar a la función saveAchievement de la siguiente manera:

Common.saveAchievement('logro_1', true);
Common.saveAchievement('logro_2', true);
...

Y para verificar si el usuario ha obtenido el logro solo tendríamos que comprobar que el valor de Common.getAchievement(name) sea true.

Es importante mencionar que para evitar errores debes iniciar Hive y abrir el box antes de iniciar cualquier operación de lectura o escritura. Recomiendan hacerlo desde el método main() como se ve en el siguiente código:

Future<Null> main() async {
await Hive.initFlutter();
await Hive.openBox('achievements');
runApp(App());
}

De todas maneras es recomendable que le des una mirada a la documentación de Hive, que para mi es de las más sencillas que he podido encontrar y super fácil de entender.

Mostrando avance de lectura

Otra cosa que hice y me gustó mucho fue guardar el avance de lectura de cada contenido, fue un reto y a la vez muy satisfactorio de lograr. Ahora todos los artículos de contenido tienen una barra amarilla que indica el avance.

Para esto lo que hice fue crear una variable de tipo ValueNotifier llamada _currentScrollPosition para almacenar en memoria la posición actual del scroll, luego metí el SingleChildScrollView dentro de un NotificationListener donde llamo a la función _scrollListener() para actualizar el valor de _currentScrollPosition.

Cabe destacar que solo actualizo la posición del scroll si el valor del mismo es mayor a la posición anteriormente guardada como se puede ver en:
if (progress > _currentScrollPosition.value)…
De esta manera evito que la barra de progreso se reduzca si el usuario hace scroll hacia arriba.

final ValueNotifier<double> _currentScrollPosition = ValueNotifier<double>(0);_scrollListener(value) {
_currentScrollPosition.value = value;
print("SCROLL POSITION: " + _currentScrollPosition.value.toString());
}
...return Scaffold(
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
double progress = scrollInfo.metrics.pixels / scrollInfo.metrics.maxScrollExtent;
if (scrollInfo is ScrollUpdateNotification) {
if (progress > _currentScrollPosition.value) {
_scrollListener(progress);
}
}
return true;
},
child: SingleChildScrollView(
...
),

Luego para mostrar la barra de progreso arriba de todo el contenido hice una pequeña trampa y metí un LinearProgressIndicator en el FloatingActionButton dentro de un ValueListenableBuilder que recibe el _currentScrollPosition que vimos arriba:

return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
floatingActionButton: ValueListenableBuilder<double>(
valueListenable: _currentScrollPosition,
builder: (context, snapshot, _){
return LinearProgressIndicator(
backgroundColor: Colors.black12,
valueColor: new AlwaysStoppedAnimation<Color>(Colors.yellow),
value: _currentScrollPosition.value,
);
}
)

One more thing…

Otra cosa que descubrí fue cómo hacer que una imagen se muestre gris, para esto metemos nuestro gráfico dentro de un widget ColorFiltered. El widget recibe el parámetro boleano earned con el que decidimos si mostrar el gráfico a colores si es true o en escala de grises si es false como se puede ver en el siguiente código:

const ColorFilter greyscaleFilter = ColorFilter.matrix(
<double>[
0.2126,0.7152,0.0722,0,0,
0.2126,0.7152,0.0722,0,0,
0.2126,0.7152,0.0722,0,0,
0,0,0,0.5,0,
]
);
return ColorFiltered(
colorFilter: this.widget.earned ? ColorFilter.mode(
Colors.transparent,
BlendMode.multiply,
)
: greyscaleFilter,
child: widget(...)
);

De aquí en adelante todo lo demás fueron detalles de diseño y algo de creatividad para agregar las insignias con sus descripciones.

Con lo anterior ya tenemos lo más básico para guardar los logros del usuario, depende de la creatividad de cada quien, pero sobre todo en la funcionalidad del app, ver qué logros se pueden incluir en la experiencia de gamification.

Puedes probar cómo quedó todo en la versión v2.3.0 del app en https://play.google.com/store/apps/details?id=co.getboosted.emprende
Déjame saber qué te parece.

Si te gustó esta publicación, házmelo saber en los comentarios. Siéntete libre de dar muchas palmadas y compartirlo como quieras :)

--

--

Nia Cubilla

Apasionada de la vida. Fan de StarWars, anime, comics, Marvel, Legofan. Panamá 🇵🇦 https://ideasdenegocios.app