Flutter API

Jun's Coding Journey·2023년 8월 18일
0

[Learn] Flutter

목록 보기
7/22

Data Fetching


Install

Before doing anything, make sure to install the http package from pub.dev.

Installing http package allows us to use methods for fetching data from an API (ex. get()).

https://pub.dev/packages/http/install

dart pub add http // install this on your terminal

 

Fetch Data

To start, create a class which can store a string
variable of the base url you want to fetch.

If you have a specific page you want to fetch data from, you can also combine that address with the base url.

import 'package:http.dart' as http;

class ApiService {
  final String baseUrl = "https://website.dev";
  final String today = "today";
}

After installing http, we can send a get request of the url we are searching for by parsing a Uri.

The Uri has a special type called a Future. A Future represents a potential value, or error, that will be available at some time in the future. They are used in Flutter to handle potentially time-consuming computations like loading a file or making a network request.

Because we want Dart to wait for information, we need to use the async and await keyword on the function.

import 'package:http.dart' as http;

class ApiService {
  final String baseUrl = "https://website.dev";
  final String today = "today";
  
  void getTodaysToons() async {
    final url = Uri.parse('$baseUrl/$today');
    await http.get(url);
  }
}

When the response is received, we need to create a variable to recieve that response for use.

We can also check whether we have successfully recieved the response or not.

With this, we are finally receiving data from the API server! :)

void getTodaysToons() async {
  final url = Uri.parse('$baseUrl/$today');
  final response = await http.get(url); // response recieved
  if (response.statusCode == 200) {
  	print(response.body);
    return;
  }
  throw Error();
}

 

fromJson


After receiving the data from the API, we now need to convert the data into useable data. In the case of Flutter, we need to convert the data into a class(json).

We can create a model which will do this conversion in a separate model file.

// Model.dart
class Model {
  final String title, thumb, id;
  
  Model({
    required this.title,
    required this.thumb,
    required this.id,
  });
}

Back in the function, we need to decode the data into a format that creates a list of json data.

void getTodaysToons() async {
  final url = Uri.parse('$baseUrl/$today');
  final response = await http.get(url); // response recieved
  if (response.statusCode == 200) {
  	final List<dynamic> data = jsonDecode(response.body);
    return;
  }
  throw Error();
}

Next, we turn our json data into class instances. We send our json data to the model for initialization.

// Model.dart
class Model {
  final String title, thumb, id;
  
  Model.fromJson(Map<String, dynamic> json)
    :  title = json['title'],
  	   thumb = json['thumb'],
       id = json['id'];
}

We store our newly converted data in a variable for future use.

void getTodaysToons() async {
  final url = Uri.parse('$baseUrl/$today');
  final response = await http.get(url); // response recieved
  if (response.statusCode == 200) {
  	final List<dynamic> datas = jsonDecode(response.body);
    for (var data in datas) {
      final newData = Model.fromJson(data);
    }
    return;
  }
  throw Error();
}

For more structure, we can create a List variable to store all the data that is converted from the model (Don't forget to return).

If there is an asynchronous error, put the Future keyword in front of the function.

Future<List<Model>> getTodaysToons() async {
  List<WebtoonModel> webtoonInstances = [];
  final url = Uri.parse('$baseUrl/$today');
  final response = await http.get(url); // response recieved
  if (response.statusCode == 200) {
  	final List<dynamic> webtoons = jsonDecode(response.body);
    for (var webtoon in webtoons) {
      webtoonInstances.add(WebtoonModel.fromJson(webtoon));
    }
    return webtoonInstances;
  }
  throw Error();
}

Wrap-up: API Data -> List of Json -> Class


 

Displaying Data on UI


Waiting for API fetch

1) Beginners Method

On the UI side, we need to make sure we wait for the data to be fetched. As such, we need to repeat the process of async and await to make sure we have fetched all the data we need.

We use the initState() function to do the waiting for us (make sure to use setState() to refresh the screen).

void waitForData() async {
  data = await ApiService.getData();
  setState(() {});
}


void initState() {
  super.initState();
  waitingForData();
}

 

2) Updated Method (FutureBuilder)

This is a much less repetitive method where we can bring our data to the UI with much less code.

We first need to specify the data we are waiting for using the Future keyword on the UI screen.

Future<List<GameModel>> data = ApiService.getTodaysGames();

We then use a widget called FutureBuilder to wait for data fetching and return the data immediately.

FutureBuilder receives the API data and waits.

The snapshot parameter uses the data and checks whether the data is available or not before returning.

body: FutureBuilder(
  future: webtoons,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return Text("There is data!");
    }
    return Text('Loading...');
  }
),

 

ListView

If we are receiving many data from an API, we can easily fetch them using a ListView() method.

This method, however, is not efficient for memory usage.

return ListView(
  children: [
  	for (var game in snapshot.data!) {
      Text(game.title)
    }
  ],
);

For memory efficiency, we use a much more optimized method called ListView.builder().

This method has much more options such as the direction we want to scroll or the amount of items we want to build in the UI screen.

Using the itemBuilder property, the ListView.builder method will only display visible data that fits in the screen to save memory.

return ListView.builder(
  scrollDirection: Axis.horizontal,
  itemCount: snapshot.data!.length!,
  itemBuilder: (context, index) {
    var game = snapshot.data![index];
    return Text(game.title);
  },
);

To put something in-between each item, we can use the ListView.separated() method to bring an addtional property call separatorBuilder.

return ListView.separator(
  scrollDirection: Axis.horizontal,
  itemCount: snapshot.data!.length!,
  itemBuilder: (context, index) {
    var game = snapshot.data![index];
    return Text(game.title);
  },
  separatorBuilder: (context, index) => SizedBox(height: 20),
);

 

GestureDetector()


The GestureDetector() widget is designed to detect events made by the user on the screen.

For instance, if a user clicks on an item on the screen, it might send the user to a different page on the screen.

Within this method, we implement the onTap property to specifiy the location of the event that will occur.

Additionally, we need to implement the Navigator.push() property to specify where want the user to go when the item is tapped. Within this method, we also implement the MateriaPageRoute() property to send the information of the item to the designated page.

NOTE: We are simply switching to another widget instead of moving to an entirely new page.

return GestureDetector(
  onTap: () {
  	Navigator.push(
      context, 
      MaterialPageRoute(builder: (context) => NewScreen(
      	title: title, 
        thumb: thumb, 
        id: id),
  },
)

 


Hero Widget


The Hero widget is used to create "hero animations." A hero animation is a common UI design pattern in which an element (typically an image or a widget) transitions smoothly between two screens, seemingly "flying" from its position on the first screen to its new position on the second screen.

This kind of animation provides a visual continuity between two UI states, making the navigation experience more intuitive and visually appealing.

// 1st Screen
Hero(
  tag: 'heroTag',
  child: Image.asset('assets/flutter_logo.png'),
)
Hero(
  tag: 'heroTag',
  child: Image.asset('assets/flutter_logo.png'),
)

For the hero animation to work:

The Hero widgets on both screens should have the same tag. This is how Flutter identifies which widgets to animate between.
There should be a navigation event between the two screens, like using Navigator.push() and Navigator.pop().
When transitioning between screens, Flutter will take care of animating the widget with the specified tag from its position on the first screen to its position on the second screen.

It's also worth noting that the Hero widget isn't limited to images; it can be used with other widgets as well. However, the key is to ensure visual continuity, so the widget should look similar (or the same) in both the source and target screens.


 

Url Launcher


Install

In Flutter, the Url_Launcher package is a plugin that allows developers to launch a URL in the mobile platform. It supports launching URLs in web browsers, making phone calls, sending SMS messages, and more.

To install Url_Launcher, we first need to install the plugin:

https://pub.dev/packages/url_launcher/install

flutter pub add url_launcher

Additionally, we need to specify the type of url we want to launch (ex. webpage, sms). The code differs between iOS and Android:

  • iOS
// Write this in your 'Info.plist' file
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>https</string>
</array>
  • Android
// Write this in your 'AndroidManifest.xml' file
<!-- Provide required visibility configuration for API level 30 and above -->
<queries>
  <!-- If your app checks for SMS support -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="sms" />
  </intent>
  <!-- If your app checks for call support -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="tel" />
  </intent>
</queries>

Implementation

Create a route to the website you want to launch to.

Use the GestureDetector() method to link the url with the button.

onButtonTap() async {
  final url = Uri.parse("https://google.com");
  await launchUrl(url);
}  
// OR

onButtonTap() async {
  await launchUrlString("https://google.com");
}

 

Storing Data


Install

To store data on the simulator, we need to install a plugin called shared_preferences

https://pub.dev/packages/shared_preferences/install

flutter pub add shared_preferences

With this plugin, we can set or read many types of data.

// Obtain shared preferences.
final SharedPreferences prefs = await SharedPreferences.getInstance();

// Save an integer value to 'counter' key.
await prefs.setInt('counter', 10);
// Save an boolean value to 'repeat' key.
await prefs.setBool('repeat', true);
// Save an double value to 'decimal' key.
await prefs.setDouble('decimal', 1.5);
// Save an String value to 'action' key.
await prefs.setString('action', 'Start');
// Save an list of strings to 'items' key.
await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);
// Try reading data from the 'counter' key. If it doesn't exist, returns null.
final int? counter = prefs.getInt('counter');
// Try reading data from the 'repeat' key. If it doesn't exist, returns null.
final bool? repeat = prefs.getBool('repeat');
// Try reading data from the 'decimal' key. If it doesn't exist, returns null.
final double? decimal = prefs.getDouble('decimal');
// Try reading data from the 'action' key. If it doesn't exist, returns null.
final String? action = prefs.getString('action');
// Try reading data from the 'items' key. If it doesn't exist, returns null.
final List<String>? items = prefs.getStringList('items');

For example, we can implement method to a 'like' button.

// Example

// Building Data Storage
late SharedPreferences prefs;
bool isLiked = false;

Future initPrefs() async {
  prefs = await SharedPreferences.getInstance();
  final likedToons = prefs.getStringList('likedToons');
  if (likedToons != null) {
  	if (likedToons.contains(widget.id) == true) {
      setState(() {
        isLiked = true;
      });
    }
  } else {
  	await prefs.setStringList('likedToons', []);
  }
}


void initState() {
  super.initState();
  webtoon = ApiService.getToonById(widget.id);
  episodes = ApiService.getLatestEpisodeById(widget.id);
  initPrefs();
}

onHeartTap() async {
  final likedToons = prefs.getStringList('likedToons');
    if (likedToons != null) {
      if (isLiked) {
        likedToons.remove(widget.id);
      } else {
        likedToons.add(widget.id);
      }
      await prefs.setStringList('likedToons', likedToons);
      setState(() {
        isLiked = !isLiked;
      });
    }
  }
  
// Rendering UI
actions: [
  IconButton(
    onPressed: onHeartTap,
    icon: Icon(
      isLiked ? Icons.favorite : Icons.favorite_outline,
    ),
  ),
],

profile
Greatness From Small Beginnings

0개의 댓글