Flutter: Building Photo App
Tags: flutter
Published: 2020-03-28 20:41:40

#### - [Course](https://www.udemy.com/share/1013AABUobdVZUQXo=/) by Stephen Grider
#### - [Project Repo](https://github.com/coltonehrman/flutter-photo-app/tree/master)

The course goes into great detail over every piece of code that is written in the App. With this walkthrough, I intend to only highlight the building process, and will not go into much detail. That part is best left to the instructor of the course. I'd like to thank him for the amazing material he releases to his students!

### Setting up Initial Project

Let's set up the initial code project after running `flutter create [app_name]`. We will clean up the default `main.dart` file and add some new folders, files to help keep things organized in the project.

To start off, we will need a `Stateful` App, that contains a simple `Scaffold` with the `appBar`, `body`, and `floatingActionButton` all set. We will also add some dummy state data.

#### New File Structure
```
lib/
  main.dart       # The main file with the 'main' function
  src/
   models/        # Will store our Dart Class data structures
   App.dart       # Our App starting point
```

**lib/main.dart**
```dart
import 'package:flutter/material.dart';
import './src/App.dart';

void main() => runApp(App());
```

**lib/src/App.dart**
```dart
import 'package:flutter/material.dart';

class App extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => AppState();
}

class AppState extends State<App> {
  int _imageCounter = 0;

  void _addImage() {
    setState(() {
      ++_imageCounter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Let\'s See Images!'),
        ),
        body: Center(
          child: Text('Image - $_imageCounter'),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: _addImage,
        ),
      ),
    );
  }
}
```

*Complete code for this section on [repo](https://github.com/coltonehrman/flutter-photo-app/tree/starter_code)*

As you can see we wrap our main Widget in a `MaterialApp` which then has a `Scaffold` to structure/layout the rest of the Widgets. We have an `AppBar` to display the App's title at the top. Some `Text` in the `Center` of the screen to show you the App's current state. Then a `FloatingActionButton` that will trigger a `setState` when pressed.

The next step will be to implement code that interacts with the image API we will be using - https://jsonplaceholder.typicode.com/photos.

We will be using the [**http**](https://pub.dev/packages/http) package to make sending the requests to the API easier to work with. *Don't forget to add the package to the projects `pubspec.yaml` file.*

Before we implement the code to interact with the API, let's set up our data model to consume the API.

#### New File
```
lib/
  src/
   models/
    ImageModel.dart # New file
```

**lib/src/models/ImageModel.dart**
```dart
class ImageModel {
  int id;
  String title;
  String url;
 
  ImageModel({ this.id, this.title, this.url });
  
  ImageModel.fromJSON(Map<String, dynamic> encodedJSON) {
    id = encodedJSON['id'];
    title = encodedJSON['title'];
    url = encodedJSON['url'];  
  }
}
```

This model will be used to consume the API that we will be using. We set up two constructors, one that can be passed the data directly, and another that will accept JSON data.

Now we are ready to fetch the API for some JSON data.

*To save on space and not clutter this article I will only be presenting updates to files that have already been created, instead of showing the whole file*

#### Updated File
```
lib/
  src/
    App.dart # Updated file
```

*Updates* to **lib/src/App.dart**
```dart
// for decoding raw json data `json.decode()`
import 'dart:convert';                   // new import
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; // new import
import './models/ImageModel.dart';       // new import

class AppState extends State<App> {
  // fetches the image JSON data from the API
  // will return the raw json decoded using `json.decode`
  dynamic _fetchImage() async {
    final imageAPI = 'https://jsonplaceholder.typicode.com/photos/$_imageCounter';
    final res = await http.get(imageAPI);
    return json.decode(res.body);
  }
  
  // handler for when `FloatingActionButton` is pressed
  void _addImage() async {
    final json = await _fetchImage();           // fetch the data from API
    final newImage = ImageModel.fromJSON(json); // consume API

    setState(() {
      ++_imageCounter;
    });
  }
}
```

*Complete code for this section on [repo](https://github.com/coltonehrman/flutter-photo-app/tree/image-api)*

Now that we have consumed the photo API, we need to create a Widget to display the data we receive.

Let's create an `ImageList` Widget to hold all the data we fetch and display something on the App with it.

#### New File
```
lib/
  src/
   widgets/ # New folder
    ImageList.dart # New file
```

**lib/src/widgets/ImageList.dart**
```dart
import 'package:flutter/material.dart';
import '../models/ImageModel.dart';

class ImageList extends StatelessWidget {
  final List<ImageModel> _images;

  ImageList(this._images);

  Widget _renderImages() {
    if (_images.length == 0)
      return Text('No Images!');
    return ListView.builder(
      itemCount: _images.length,
      itemBuilder: (BuildContext context, int index) {
        return Text(_images[index].title);
      }
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: _renderImages(),
      ),
    );
  }
}
```

We are using the `ListView.builder` constructor to build a dynamic scrollable list for the images. We still do not have a proper Image UI in place. But, now you visually see the API being consumed by the application upon user interaction.

Next, we will update the *App.dart* file to use our new custom Widget.

#### Updated File
```
lib/
  src/
    App.dart # Updated File
```

*Updates* to **lib/src/App.dart**
```dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import './models/ImageModel.dart';
import './widgets/ImageList.dart'; // new import

class AppState extends State<App> {
  dynamic _fetchImage() async {
    // moved the imageCounter incrememting to here to prevent
    // consecutive calls overlapping before the Future resolves
    // and making app fetch the same image more than once
    final imageAPI = 'https://jsonplaceholder.typicode.com/photos/${_imageCounter++}';
    final res = await http.get(imageAPI);
    return json.decode(res.body);
  }
  
  void _addImage() async {
    final json = await _fetchImage();
    final newImage = ImageModel.fromJSON(json);
    // modified setState
    setState(() => _images.add(newImage));
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Let\'s See Images!'),
        ),
        body: Center(
          // updated child to be our custom Widget
          child: ImageList(_images),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: _addImage,
        ),
      ),
    );
  }
}
```

We have updated our App to include the new custom Widget we built, and also fixed a small bug that caused the `_fetchImage` handler to be called consecutively and fetch the same image more than once. *See if you can figure out what was causing the bug, and why the new code fixes it.*

*To be continued...*

#### Follow my journey of learning flutter!
{% link coltonehrman/learning-journey-flutter-488 %}