CategoriasAndroid

Como usar Android ExoPlayer con Jetpack Compose

Jetpack Compose ha venido para quedarse, es la solución moderna al desarrollo de interfaces de aplicaciones Android. Utilizando toda la potencia de Kotlin y su compilador, se ha logrado crear un Toolkit poderoso, fácil de utilizar y extensible. En este artículo vamos a hablar sobre cómo podemos utilizar Jetpack Compose con vistas existentes de Android como lo es Exoplayer.

¿Que es exoplayer? Es una librería de Android que contiene un reproductor que nos permite reproducir en nuestras aplicaciones de Android, videos, audio y streaming. Además de estas funcionalidades, también nos permite gestionar listas de reproducción, lo cual siempre se agradece.

Esta librería fue creada utilizando el toolkit de UI de Android existente, conocido también como Android Views, el cual es un mundo muy diferente al que manejaremos ahora y en el futuro con Jetpack compose. Para poder tener esta interoperabilidad entre las anteriores views y Jetpack Compose, existe un composable especial llamado Android View.

CategoriasFlutterProgramación

📸 Como tomar una foto con Flutter o elegir una foto de la galería.

¡Hola! Ultimamente he estado trabajando en una app que requiere tomar una foto desde la app o de la galeria y mostrarla al usuario. He aprendido un par de cosas implementando esta feature y he querido compartirlo por acá. Existen varias formas de tomar una foto utilizando la cámara pero en esta oportunidad vamos a utilizar el imagePicker plugin.

Empecemos por las dependencias.

Como primer paso, vamos a agregar los plugins a nuestras dependencias. Abrimos nuestro pubspec.yaml y agregamos los siguientes plugins:

  • camera: Este plugin nos ayudará a trabajar con las cámaras de los dispositivos.
  • path_provider: Este plugin nos proporciona el path correcto para saber donde almacenar las imagenes en nuestro dispositivo, ya que cambian entre una plataforma y otra.
  • image_picker este nos ayuda a seleccionar una foto de la galería.

Despues de agregar estos plugins nuestro pubspec.yaml debería verse así.

name: fluttercamera
description: A Flutter project that takes a picture and shows the preview.
version: 1.0.0+1

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  camera:
  path_provider:
  path:
  image_picker:

  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:

  uses-material-design: true

Aumentamos la versión minima de Android

El plugin de camera en Flutter solo funciona con sdk 21 en adelante en Android, lo que quiere decir que solo dispositivos Android con sistema operativo Lollipop o superior pueden utilizar una app con este plugin.

Para esto vamos a abrir el archivo build.gradle ubicado en android/app/build.gradle y buscar la linea que dice «minSdkVerrsion«. Finalmente cambiamos la versión de 16 a 21.

Luego de cambiar la versión debería verse así

¡Creemos nuestro primer Screen!

Para empezar vamos a crear un StatefulWidget llamado PhotoPreviewScreen. Debería verse de esta manera.

class PhotoPreviewScreen extends StatefulWidget {
  @override
  _PhotoPreviewScreenState createState() => _PhotoPreviewScreenState();
}

class _PhotoPreviewScreenState extends State<PhotoPreviewScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(

    );
  }
}

Ahora, vamos a crear un Scaffold con un widget Column centrado el cual va a mostrar una vista previa de nuestra imagen después que tomemos una foto o la seleccionemos de la galería. Y como paso final vamos a agregar un floatingActionButton, que al tocar nos mostrará un dialogo con las opciones para elegir una foto de la galería o desde la cámara.

class PhotoPreviewScreen extends StatefulWidget {
  @override
  _PhotoPreviewScreenState createState() => _PhotoPreviewScreenState();
}

class _PhotoPreviewScreenState extends State<PhotoPreviewScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            _setImageView()
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _showSelectionDialog(context);
        },
        child: Icon(Icons.camera_alt),
      ),
    );
  }

¡Genial! Ahora seguramente te estés preguntando qué hace el _setImageView() y el _showSelectionDialog(Context).

El _setImageView() es un método que devuelve un Image Widget en caso de que la imagen obtenida no sea nula, de lo contrarío devolverá un Text Widget con un mensaje de Error. El método _showSelectionDialog(Context) muestra un dialogo con dos opciones, seleccionar una imagen de galería o de la cámara.

Empecemos creando el último, este método debe usar la función showDialog() y pasarle el Context y un Builder el cual va crear un AlertDialog con un titulo y dos opciones.

En el constructor del AlertDialog, vamos a pasar como contenido un SingleChildScrollView el cual es un Widget que nos ayudará a hacer scroll en la lista, y como child de este Widget vamos a pasarle un ListBody con dos GestureDetector como hijos para detectar cuándo el usuario ha tocado el texto.

Cada GestureDetector va a tener un Text Widget con el texto «Galería» y «Cámara» respectivamente. Y cada Text widget tambíen tendrá como parámetro de su función onTap el método _openGallery() y openCamera() segun corresponda. Estos métodos los crearemos más adelante. Al terminar el método _showSelectionDialog(context) estará así.

Future<void> _showSelectionDialog(BuildContext context) {
    return showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
              title: Text("From where do you want to take the photo?"),
              content: SingleChildScrollView(
                child: ListBody(
                  children: <Widget>[
                    GestureDetector(
                      child: Text("Gallery"),
                      onTap: () {
                        _openGallery(context);
                      },
                    ),
                    Padding(padding: EdgeInsets.all(8.0)),
                    GestureDetector(
                      child: Text("Camera"),
                      onTap: () {
                        _openCamera(context);
                      },
                    )
                  ],
                ),
              ));
        });
  }

Ahora al tocar nuestro FloatingActionButton debería mostrar un Dialog como este.

Ahora a utilizar el plugin ImagePicker

Ahora vamos a agregarle la lógica a nuestro método _openGallery(context). Lo primero que vamos a hacer es crear un Field llamado imageFile, el cual va a ser una variable de tipo File en nuestro _LandingScreenState. Luego vamos a utilizar la función del ImagePicker, llamada pickImage() y le vamos a pasar como parámetro el enum ImageSource.gallery. Esta función es asíncrona así que vamos a tener que utilizar las palabras reservadas async y await. Luego vamos a almacenar esta variable y asignarla a nuestra variable imageFile. Como paso final, vamos a llamar al método setState() para notificar que el State ha cambiado. Y nuestra función deberá verse así.

void _openGallery(BuildContext context) async {
    var picture = await ImagePicker.pickImage(source: ImageSource.gallery);
    this.setState(() {
      imageFile = picture;
    });
    Navigator.of(context).pop();
  }

La función _openCamera(Context) es casi igual, la unica diferencía que tiene al respecto es que esta utiliza el enum imageSource.camera en lugar de gallery. Al realizar este cambio tu función de _openCamera() deberá verse así.

void _openCamera(BuildContext context) async {
    var picture = await ImagePicker.pickImage(source: ImageSource.camera);
    this.setState(() {
      imageFile = picture;
    });
    Navigator.of(context).pop();
  }

Previsualización

¿Recuerdas aquella función que mencionamos al principio llamada _setImageView()? Pues no podemos olvidarnos de ella. Para ello vamos a verificar si nuestra variable imageFile es distinta de null. Si lo es, devolvemos un Image Widget con la variable que teniamos almacenada y si aún es nula pues devolvemos un texto que diga que debemos seleccionar una imagen.

Así se vería la función terminada.

Widget _setImageView() {
    if (imageFile != null) {
      return Image.file(imageFile, width: 500, height: 500);
    } else {
      return Text("Please select an image");
    }
  }

Así es como se vería la app al final, luego de seleccionar una imagen de la galería.

Eso es todo

Espero que te haya gustado este artículo. Si te interesan mas contenido sobre Flutter o Dart puedes seguirme en las redes sociales.

Instagram.

Twitter.

🤓Adémas no te pierdas ningun artículo uniendote a mi newsletter

Puedes registrarte haciendo click acá debajo 👇.

😄 Haz click aquí para unirte a la newsletter

CategoriasProgramación

Que es un unit test

Que es un unit test (Prueba unitaria)

Hola! Probablemente hayas visto en ofertas de trabajo la siguiente frase, «Experiencia con pruebas unitarias y TDD.» Pero si eres como yo y no tienes idea sobre tests, has oído de ellos, sabes que existen y sabes que las buenas compañías lo usan. Pero, ¿qué son y cómo funcionan?

🤔 Empecemos con un test

Cuando alguien de QA (Quality assurance, aquellos que prueban tu app y encuentran fallos que tu no conocías) se acerca a mi y me pregunta si estoy seguro sobre lo que hace mi código, mi respuesta siempre es no, y no es porque no confié en mis habilidades de programación, es porque soy humano y como todo ser humano cometemos errores. Es por esto que empece a aprender sobre testing ya que los test pueden ayudarte a conseguir los errores que vas a introducir en el futuro.

Un test ayuda a probar algo, en nuestro caso, que nuestro código vaya como lo hemos planeado. Con mis simples palabras podría decir que un test es código que tu escribes para verificar que el código que vas a tener en producción funciona y hace lo que esperas que haga.

👩‍💼👨‍💼Unidad

Una unidad es algo individual, digamos que un elemento, muchos elementos pueden crear algo mas grande que ellos. En nuestro caso, una unidad puede ser una clase o una función la cual junto con otras clases u otras funciones pueden crear una app mas compleja.

Al hacer pruebas de cada una de las unidades estamos haciendo pruebas unitarias.

👩‍💼👨‍💼 La prueba unitaria

Digamos que vamos a crear una nueva, nunca hecha, disruptiva app llamada calculadora. En esta app tendremos una función llamada sum(), la cual realiza la suma de dos números. Si lo hacemos en Kotlin, sería:

fun sum(int a,int b) : Int {
    return a+a;
}

Luego nos vamos a crear nuestra UI (Interfaz de usuario) la cual es fantástica y amamos hacer para que quede todo super lindo, pero cundo corremos la app y empezamos a probarla, nos damos cuenta de que la función de suma no funciona. ¿Por qué no funciona? Si los parámetros los he declarado bien y la suma va bien. Créeme, esto ocurre mucho mas de lo que imaginas.

Y es un problema si tus build times toman cierto tiempo. Ejecutar una compilación que tarde mucho puede romper el estado de concentración en el que te encontrabas, y espero que no seas como yo quien se distrae fácilmente y pierde el foco de que estaba haciendo. Por suerte, las pruebas unitarias pueden ayudarnos a solucionar estos dos problemas ya que los tests se encargan de verificar que el código haga lo que supone que debe hacer y como las pruebas unitarias son mas rápidas que compilar toda la app y probar cada cosa manualmente nos vienen de maravilla. Así que para empezar, vamos a crear nuestro primer test.

Casi todos los lenguajes tienen un framework para tests. Puedes revisar cual es el framework para test disponibles para tu lenguaje **aquí.** En nuestro caso como estoy usando Kotlin, vamos a usar JUnit.

Una prueba unitaria se parece a esto y por defecto está en la carpeta de tests del proyecto.

    @Test
    fun calculator_sum_shouldReturnTheSumBetweenTwoParameters() {
        //Given
        val calculator = Calculator()
        //When
        val result = calculator.sum(2, 1)
        //Then
        assertEquals(3,result)
    }

Analicemos un poco qué contiene este Test.

  • La anotación @Test que indica que es un Test.
  • El nombre de la función que indica [Unidad_NombreDeLoQueEstamosProbando_ResultadoEsperado], esto en ingles se hace de la siguiente forma: [Unit_WhatWeAreTesting_ExpectedBehavior].
  • El contenido del test.

Nuestro test debe seguir el patrón Arrange-Act-Assert (Organizar, actuar y afirmar) o el Given- When-Then (Dado que, cuando, entonces). A mi me gusta el patrón given, when, then debido a que me recuerda mucho a lo que suelo ver en las historias de usuario. Además, si te distraes puedes seguir los pasos y recordar el paso que te falta. Hablemos un poco sobre este patrón.

  • Given: En esta parte del patrón, los objetos que vas a necesitar son creados.
  • When: En este paso, llamamos a la función que queremos probar.
  • Then: Finalmente, en la ultima sección, hacemos la comprobación para evaluar el resultado.

Veamos un ejemplo:

Alt Text

En mi caso estoy usando Intellij Idea, en este IDE tengo un botón de play cerca de mi test y al hacerle click podremos ver en la siguiente imagen cómo el test falla.

Alt Text

Nuestro test ha fallado porque nuestra función está sumando dos veces el primer parámetro 🤦‍♂. Ahora que ya sabemos qué ocurre podremos cambiar nuestra función y ejecutar de nuevo el test para ver cómo pasa.

fun sum(int a,int b) : Int {
    return a+b;
}

Alt Text

Increíble. Tengo que admitir que ahora el verde se ha vuelto uno de mis colores favoritos ya que es el color de los tests al pasar.

Nota de Kotlin: En Kotlin, podemos utilizar las comillas invertidas como nombre de la función y hacer nuestro Test mas legible!

@Test
    fun `calculator sum should Return The Sum Between Two Parameters`() {
        //Given
        val calculator = Calculator()
        //When
        val result = calculator.sum(2, 1)
        //Then
        assertEquals(3,result)
    }

🔺 La pirámide del testing

Hemos hablado sobre qué es un unit test, pero hay más tipos de test.

  • Unit Test, se encargan de probar una unidad en especifico.
  • Test de integración, verifica que dos o más unidades estén funcionando juntas correctamente como deberían.
  • End to End tests, verifican que todas las unidades están funcionando correctamente. En el caso de android esto incluye probar la UI.

Visto que tenemos varios tipos de test, debemos dividirlos razonablemente. No todos los tests pueden ser End to End o test de integración. Por suerte, hay una imagen que explica muy bien cómo debemos distribuir los tests y se llama la pirámide del testing.

Alt Text

La pirámide del testing fue creada por Mike Cohn en su libro, Succeeding with Agile. Y en este, comenta que los unit test deberían ser la base de la pirámide seguidos por los test de integración y finalmente los End to End.

🤓 Otros beneficios del testing

Tener una test suite te puede hacer de malla protectora y ayudarte a detectar bugs que creas cuando estés programando una nueva funcionalidad o cuando estés haciendo un refactor. Además, tus tests pueden ayudar a futuros desarrolladores a entender mas el código que has creado ya que un test es la mejor documentación que hay.

¡Eso es todo por ahora! Espero que te haya gustado este post. Si te ha gustado, compártelo con tus amigos y compañeros de trabajo así todos podemos aprender un poco mas de testing.

Quieres saber más

Si te ha gustado este artículo y quieres saber sobre otros temas que te interesen puedes dejar un comentario o escribirme a cualquiera de mis redes sociales.

🐦 Twitter

📘 Facebook

📷 Instagram

CategoriasSin categoria

Guardar en base de datos con Flutter

Guardar en base de datos con Flutter

Photo by Tim Evans on Unsplash.

Almacenar texto en la base de datos es junto a las llamadas a red, una de las operaciones que como desarrolladores más tenemos que realizar. Algunas veces, necesitamos guardar la información de los servidores en nuestra app para que los usuarios puedan abrir la app y ver información previamente guardada antes de que se actualice con nuevo contenido, o podemos también tener que implementar un modo offline.

En este artículo, vamos a hablar sobre cómo almacenar información en una app con Flutter. Nuestra app será una aplicación que almacenará unas Tareas por realizar (Todo’s), las cuales podremos consultar luego.

Una lista de las cosas que vamos a necesitar:

  • Una clase de Tareas por realizar (De ahora en adelante las llamaremos Todo).
  • Una Screen, la cual es un Widget que nos mostrará unos campos donde podremos crear nuestro Todo.
  • Agregar las dependencias de los paquetes que utilizaremos. Un paquete es lo que en otros lenguajes puede conocerse como librería, nosotros utilizaremos sqflite.
  • Una clase DatabaseHelper la cual se encargará de gestionar la base de datos.

https://www.codingpizza.com/wp-content/uploads/2020/01/Screenshot_1578338767.png

Primero, vamos a crear nuestra clase Todo.

class Todo {
  final int id;
  final String content;
  final String title;
  static const String TABLENAME = "todos";

  Todo({this.id, this.content, this.title});

  Map<String, dynamic> toMap() {
    return {'id': id, 'content': content, 'title': title};
  }
}

Esta es la clase Todo la cual contiene un id, el contenido del Todo y el título. Además, agregamos una constante que incluye el nombre de la tabla de Todos, así como también una función toMap que utilizaremos más adelante.

Con la clase Todo creada, vamos a agregar nuestra dependencia de la siguiente forma en el archivo pubspec.yml.

dependencies:
  flutter:
    sdk: flutter
  sqflite:
  path:

Ahora tenemos que crear nuestra clase DatabaseHelper, la cual tiene la función de tratar con la base de datos.

class DatabaseHelper {
  //Create a private constructor
  DatabaseHelper._();

  static const databaseName = 'todos_database.db';
  static final DatabaseHelper instance = DatabaseHelper._();
  static Database _database;

  Future<Database> get database async {
    if (_database == null) {
      return await initializeDatabase();
    }
    return _database;
  }

  initializeDatabase() async {
    return await openDatabase(join(await getDatabasesPath(), databaseName),
        version: 1, onCreate: (Database db, int version) async {
      await db.execute(
          "CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title TEXT, content TEXT)");
    });
  }
}

Tenemos varias cosas de las que hablar sobre esta clase. Para empezar, acceder a una base de datos es una operación asíncrona, lo que significa que debemos utilizar Futures para inicializar la propiedad database.

En la función initializeDatabase vamos a inicializar una base de datos siempre y cuando no tengamos una ya creada previamente, para inicializarla vamos a llamar la función initializeDatabase y dentro de ella llamaremos a la función openDatabase de la librería de sqflite.

La función openDatabase necesita los siguientes parámetros:

  • Un String que sería el Path de la base de datos. Para esto, debido a que tratamos con sistemas operativos diversos, hay que utilizar la función join() que se encargará de unir los path que vamos a obtener con la función getDatabasesPath().
  • Una función anónima que será ejecutada cuando la base de datos sea creada. En nuestro caso, una función anónima con una query creará una tabla dentro de la base de datos.

La función insertTodo() toma como parámetro un objeto Todo. Obtiene la base de datos que habíamos creado previamente y utiliza la función de la librería sqflite execute() para insertar los datos en nuestra base de datos. A esta función le vamos a pasar los siguientes parámetros.

  • El nombre de la tabla en la cual vamos a insertar la información. En nuestro caso tenemos el nombre declarado como una constante en nuestra clase de Todo.
  • Un map que incluye la información que queremos insertar. Para esto hemos creado la función toMap de nuestra clase Todo que convierte nuestro objeto Todo a un Map.
  • Y una constante definida por la librería que especificará qué acción tomar cuando se encuentre un conflicto. Nosotros vamos a tomar la acción de reemplazar siempre que tenga un conflicto.

Finalmente, podemos crear nuestra Screen la cual nos servirá para crear nuestro Todo object. Dado que los campos serán editables necesitaremos utilizar un StatefulWidget con su respectivo State. Los widgets que vamos a utilizar son Textfields y FloatingActionButton para guardar el Todo.

class CreateTodoScreen extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _CreateTodoState();
  }
}

class CreateTodoScreen extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _CreateTodoState();
  }
}

class _CreateTodoState extends State<CreateTodoScreen> {
  final descriptionTextController = TextEditingController();
  final titleTextController = TextEditingController();

  @override
  void dispose() {
    super.dispose();
    descriptionTextController.dispose();
    titleTextController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Retrieve Text Input'),
      ),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Title"),
              maxLines: 1,
              controller: titleTextController,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Description"),
              maxLines: 10,
              controller: descriptionTextController,
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.check),
          onPressed: () {
            DatabaseHelper.instance.insertTodo(Todo(
              title: titleTextController.text,
              content: descriptionTextController.text));
            Navigator.pop(context, "Your todo has been saved.");
          }),
    );
  }
}

Luego de crear nuestro State necesitamos utilizar un TextEditingController para cada uno de nuestros TextFields, estos los declaramos al principio de nuestro Widget y hacemos un override de la función dispose() para llamar a la respectiva función dispose de cada TextEditingController.

Al momento de crear nuestros TextFields, le pasamos a cada TextField su respectivo controller. Finalmente, para llamar al DatabaseHelper e indicarle que llame a la función insertTodo debemos pasarle a la propiedad onPressed del floatingActionButton una función anónima con un Todo creado usando el texto obtenido de los TextEditingController.

https://www.codingpizza.com/wp-content/uploads/2020/01/Screenshot_1578338787.png

Actualizando nuestros Todo’s

Para actualizar nuestros Todo’s necesitamos hacer ciertas modificaciones. Primero, vamos a agregar esta nueva función a nuestro DatabaseHelper.

updateTodo(Todo todo) async {
    final db = await database;

    await db.update(Todo.TABLENAME, todo.toMap(),
        where: 'id = ?',
        whereArgs: [todo.id],
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

En esta función obtenemos el objeto database y luego llamamos a la función update() de la librería y le pasamos los siguientes parámetros.

  • El nombre de la tabla que hemos declarado previamente en nuestro objeto Todo.
  • El objeto Todo convertido a un map usando la función toMap.
  • La cláusula where la cual aplicará cambios en cualquier fila que cumpla con esa condición
  • El parámetro whereArgs que sustituirá el signo de interrogación en nuestra cláusula where.
  • Y finalmente, una constante que especifica el tipo de algoritmo a usar en caso de que haya un conflicto.

Ahora debemos actualizar nuestra UI. Primero, vamos a modificar nuestra DetailTodoScreen para poder editar nuestro Todo. Necesitaremos agregar nuestro Todo como un parámetro opcional en el constructor del DetailTodoScreen, luego crear una property Todo en nuestro Widget y debido a que es un StatefulWidget debemos pasarle este Todo al State.

class DetailTodoScreen extends StatefulWidget {
    final Todo todo;

  const DetailTodoScreen({Key key, this.todo}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _CreateTodoState(todo);
}

En nuestro State necesitamos agregar también nuestro Todo al constructor, crear la property Todo y sobrescribir la función initState de nuestro Widget para que podamos asignar el Título y la descripción de nuestro Todo a los respectivos TextField.

class _CreateTodoState extends State<DetailTodoScreen> {
  Todo todo;
  final descriptionTextController = TextEditingController();
  final titleTextController = TextEditingController();

  _CreateTodoState(this.todo);

  @override
  void initState() {
    super.initState();
    if (todo != null) {
      descriptionTextController.text = todo.content;
      titleTextController.text = todo.title;
    }
  }

Finalmente, en la función anónima que le pasamos como parámetro onPressed vamos a llamar a otra función llamada saveTodo, la cual se encargará de insertar un nuevo Todo en caso de que no tengamos ningún Todo para editar o de modificar un Todo existente que hayamos pasado como parámetro a nuestro State. Al final debemos llamar también a la función setState.

Así es como se verá nuestro State luego de esas modificaciones.

class _CreateTodoState extends State<DetailTodoScreen> {
  Todo todo;
  final descriptionTextController = TextEditingController();
  final titleTextController = TextEditingController();

  _CreateTodoState(this.todo);

  @override
  void initState() {
    super.initState();
    if (todo != null) {
      descriptionTextController.text = todo.content;
      titleTextController.text = todo.title;
    }
  }

  @override
  void dispose() {
    super.dispose();
    descriptionTextController.dispose();
    titleTextController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Retrieve Text Input'),
      ),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Title"),
              maxLines: 1,
              controller: titleTextController,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), labelText: "Description"),
              maxLines: 10,
              controller: descriptionTextController,
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.check),
          onPressed: () async {
            _saveTodo(titleTextController.text, descriptionTextController.text);
            setState(() {});
          }),
    );
  }

  _saveTodo(String title, String content) async {
    if (todo == null) {
      DatabaseHelper.instance.insertTodo(Todo(
          title: titleTextController.text,
          content: descriptionTextController.text));
      Navigator.pop(context, "Your todo has been saved.");
    } else {
      await DatabaseHelper.instance
          .updateTodo(Todo(id: todo.id, title: title, content: content));
      Navigator.pop(context);
    }
  }
}

Deleting a Todo

Para borrar un Todo necesitamos comenzar por borrar una función de nuestra clase DatabaseHelper.

deleteTodo(int id) async {
    var db = await database;
    db.delete(Todo.TABLENAME, where: 'id = ?', whereArgs: [id]);
  }

Esta función llama a la función delete de la librería Sqflite a la cual le pasamos el nombre de la tabla la cláusula where y el id del Todo a eliminar.

Luego, para agregar una función de borrado en nuestra lista de todos vamos a necesitar realizar los siguientes pasos. Primero, en nuestro ListTile widget vamos a necesitar a utilizar un parámetro opcional llamado trailing, en este parámetro vamos a pasarle un IconButton, dentro de ese parámetro onPressed vamos a pasarle una función anónima que llamará a la función del DatabaseHelper encargada de borrar el Todo.

class  ReadTodoScreen extends StatefulWidget {
  @override
  _ReadTodoScreenState createState() => _ReadTodoScreenState();
}

class _ReadTodoScreenState extends State<ReadTodoScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Saved Todos'),
      ),
      body: FutureBuilder<List<Todo>>(
        future: DatabaseHelper.instance.retrieveTodos(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data.length,
              itemBuilder: (BuildContext context, int index) {
                return ListTile(
                  title: Text(snapshot.data[index].title),
                  leading: Text(snapshot.data[index].id.toString()),
                  subtitle: Text(snapshot.data[index].content),
                  onTap: () => _navigateToDetail(context, snapshot.data[index]),
                  trailing: IconButton(
                      alignment: Alignment.center,
                      icon: Icon(Icons.delete),
                      onPressed: () async {
                        _deleteTodo(snapshot.data[index]);
                        setState(() {});
                      }),
                );
              },
            );
          } else if (snapshot.hasError) {
            return Text("Oops!");
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

_deleteTodo(Todo todo) {
  DatabaseHelper.instance.deleteTodo(todo.id);
}

_navigateToDetail(BuildContext context, Todo todo) async {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => DetailTodoScreen(todo: todo)),
  );
}

https://www.codingpizza.com/wp-content/uploads/2020/01/Screenshot_1578338793.png

¡Y eso es todo! Ahora tenemos un app que puede almacenar un Todo, leerlo desde base de datos, actualizarlo y borrarlo.

El código está disponible aquí. Si quieres leer más artículos como estos o tienes alguna duda, puedes encontrarme en las siguientes redes sociales.

Instagram: www.instagram.com/codingpizza
Twitter: www.twitter.com/coding__pizza
Facebook: www.facebook.com/codingpizza
Site: www.codingpizza.com

CategoriasProgramación

¡Algoritmos!, ¿Que son?

¡Hola! Soy Giuseppe Vetri un fanático de la computación, ingeniero en computación y apasionado por el desarrollo. Este es mi primer post en este blog llamado CodingPizza y mi primer post en general quise abrir este blog para contar un poco acerca de mis experiencias con el desarrollo y poder colaborar con otros desarrolladores que estén aprendiendo o se encuentren en un nivel mas avanzado acerca de software hablar sobre las buenas practicas y entender juntos algunas documentaciones que no son del todo amigables.Bueno, Basta de hablar de mi. Hablemos del tema de hoy el cual es ¡Algoritmos!, ¿Que son?  ¿Para que se usan? si has llegado hasta aquí a este blog seguramente tienes alguna noción de programación. De no ser así no te preocupes que aquí lo hablaremos desde el principio.

La palabra algoritmo te puede sonar a computación, a código, a programación. Pero ¿sabias que usamos algoritmos todos los días?, Pues si.

Los algoritmos son una serie ordenada de instrucciones o procesos que se realizan para solucionar un problema, pueden ser algo sencillo como tus rutinas matutinas por ejemplo:

  1. Levantarte de la cama
  2. Cepillarte
  3. Bañarte
  4. Vestirte
  5. Desayunar
  6. Salir a trabajar

Vemos y realizamos algoritmos todos los días, ahora si lo llevamos a ciencias de la computación podemos decir que los algoritmos son una serie de instrucciones con ordenes muy precisas y escritas en un lenguaje de programación que el computador entienda y pueda realizar las ordenes que le estamos indicando.

Existen algoritmos que son muy importante en el campo de la programación los cuales son:

Algoritmos de Ordenamiento

Son algoritmos que se basan en darle una secuencia a ciertos elementos determinados los cuales podrían por ejemplo ser ordenados numéricamente. Por nombre a un par de ellos podemos mencionar al Quick Sort el cual se basa en comparar elementos para determinar su orden y el Radix Sort o ordenamiento de raíz, el cual toma el ultimo dígito significativo de cada elemento y los va agrupando consecutivamente.

Ejemplo del Quick Sort

Un Ejemplo del Radix Sort se puede encontrar en la pagina de la Universidad de San Francisco: Radix Sort Visualization
Arboles

En las ciencias de la computación un árbol es un tipo de dato abstracto o una estructura de datos que su función es simular la copa de los arboles empezando desde un padre hasta tener muchos nodos los cuales son los hijos y el nodo que no tenga hijo es llamado Hoja. Para que exista una estructura de árbol valido un nodo del árbol no puede tener mas de 1 padre ademas que no pueden haber nodos sin conectarse.

 

Arbol binario

Ejemplo de un árbol binario

Listas

Una lista es un tipo de dato abstracto como lo son los arboles, el cual contiene un numero de valores secuenciales donde los valores pueden repetirse a diferencia de las pilas o las colas los valores pueden ser removidos en cualquier posición de la lista. Existen varios tipos de listas como lo son las Listas Enlazadas(Linked List) las cuales son una secuencia de valores que tienen una referencia hacia el valor que le antecede y el valor que le procede.

Lista Enlazada

Ejemplo de Listas Enlazadas

Stacks

Los stacks o pilas, son estructuras de datos con las que se puede encontrar fácilmente una analogía en la vida real, por ejemplo podemos decir que tenemos una pila de libros de programación en el siguiente orden:

  • Introducción a Python
  • Como volverse un experto en Ruby
  • Aprendiendo Javascript
  • Libro de programación de Kotlin

Ahora queremos retirar el libro de programación de Kotlin a diferencia de las listas en las que se puede retirar un elemento de cualquier posición de esta con las pilas no ocurre lo mismo. En este caso solo podremos obtener el libro que se encuentra en la cima de la pila e ir retirando continuamente hasta poder alcanzar el libro que deseamos que en nuestro caso seria el de Kotlin (Para los que aun no conocen este lenguaje pronto aquí en CodingPizza tendremos un tutorial sobre este lenguaje de programación).

En las pilas solo podemos retirar elementos que están en la cima de esta debido a que tienen una estructura del tipo LIFO (Last In First Out) esto significa que el ultimo elemento en entrar a la pila es el primero en salir a diferencia de las listas las cuales son del tipo FIFO ( First In First Out) como ocurre en las filas de los supermercados el primero en llegar es el primero en salir.

 

Pila

Representación grafica de un Stack o Pila

Algoritmos de Grafos

Los algoritmos de grafos son muchos, uno de los mas famosos es el algoritmo de Dijkstra el cual tiene como función encontrar el camino mas corto dado un vértice de inicio. Este algoritmo funciona de la siguiente manera selecciona la arista con peso menor que contenga el vértice y lo repetirá cuantas veces sea necesario para llegar a su destino.

Ejemplo de como funciona un algoritmo de grafo

Conclusión
Existen muchas variaciones de estos algoritmos de los cuales hemos hablado, algunos son mejores que otros dependiendo del contexto en el cual se este usando. En la programación siempre habrá una herramienta que funcionara bien para cierto tipos de problemas y otra que funcionaria mejor para otros.

¿Que algoritmo te ha gustado mas? ¿Has tenido que implementar uno para una prueba practica para un trabajo o la universidad? Nos encantaría saber acerca de tu experiencia 🙂

¡Comenta y si te ha gustado el post compártelo en las redes sociales!

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.

ACEPTAR
Aviso de cookies