NCMBのFlutter SDKを使ってニュースアプリを作る（その2：ニュースの取得と表示）

NCMBでは公式SDKとしてSwift/Objective-C/Java/Unity/JavaScript SDKを用意しています。また、それ以外にもコミュニティSDKとして、非公式ながらFlutter/React Native/Google Apps Script/C#/Ruby/Python/PHPなど幅広い言語向けにSDKが開発されています。

今回はコミュニティSDKの一つ、Flutter SDKを使ってニュースアプリを作ってみます。[前回は画面の仕様とSDKの初期化について解説しました](https://qiita.com/goofmint/items/d161226bfa10cfe18652)。今回はニュースの取得と、一覧表示までを実装します。

## 完成版のコード

作成したデモアプリのコードは[NCMBMania/flutter_news: Flutter SDKを使ったニュースアプリデモです。](https://github.com/NCMBMania/flutter_news)にアップロードしてあります。

## ニュース記事をNCMBに登録する

まずニュース記事をNCMBに登録します。この時、RSSフィードを直接読み込んでも良いですが、キャッシュであったりデータへのアクセスのしやすさを考えると一旦NCMBに保存する方がお勧めです。

今回はNCMBのスクリプト機能とRSS2JSONを使ってフィードをJSON化しつつ、データストアに取り込んでいます。YOUR_APPLICATION_KEYとYOUR_CLIENT_KEYを、それぞれ皆さんのものと置き換えてください。

```js
// ライブラリの読み込み
const request = require('superagent');
const NCMB = require('ncmb');
// 定数を定義
const applicationKey = 'YOUR_APPLICATION_KEY';
const clientKey = 'YOUR_CLIENT_KEY';

// NCMBの準備
const ncmb = new NCMB(applicationKey, clientKey);
const Feed = ncmb.DataStore('Feed');
const Entry = ncmb.DataStore('Entry');

// メイン処理
module.exports = async (req, res) => {
  if (!req.query.url) {
    return res.json({});
  }
  // キャッシュの検索
  const date = new Date;
  date.setHours(date.getHours() - 1);
  let feed = await Feed.equalTo('url', req.query.url).fetch();
  if (Object.keys(feed).length > 0) {
    if (new Date(feed.fetchDate.iso) > date) {
      res.json({
        objectId: feed.objectId,
        nextFetchDate: new Date(feed.fetchDate.iso)
      });
      return;
    }
  } else {
    feed = new Feed;
    feed.set('url', req.query.url);
  }
  // フィードを取得
  const url = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURI(req.query.url)}`;
  response = await request
    .get(url)
    .send();
  const json = await response.body;
  for (const key in json.feed) {
    if (key !== 'items' && key !== 'url') {
      feed.set(key, json[key]);
    }
  }
  
  // フィールの中の記事を検索&登録
  const entries = [];
  const relation = new ncmb.Relation();
  for (const item of json.items) {
    let entry = await Entry.equalTo('guid', item.guid).fetch();
    if (Object.keys(entry).length > 0) {
      relation.add(entry);
    }
    entry = new Entry;
    for (const key in item) {
      if (['created', 'updated'].indexOf(key) > -1) {
        entry.set(key, new Date(item[key]));
      } else {
        entry.set(key, item[key]);
      }
    }
    relation.add(entry);
  }
  
  // フィードを更新
  feed.set('entries', relation);
  feed.set('fetchDate', new Date);
  const method = feed.objectId ? 'update' : 'save';
  try {
    await feed[method]();
    res.json({
      objectId: feed.objectId,
      nextFetchDate: feed.fetchDate
    });
  } catch (e) {
    console.log(e);
    res.json(e);
  }
}
```

実際に呼び出す際にはGETリクエストで、urlパラメータに対してフィードのURLを指定して実行します。このスクリプトを実行すると、ニュース記事がFeedクラスとEntryクラスに登録されます。

## ニュース記事を一覧表示する

登録したニュース記事を一覧表示するのが _MyHomePageState クラスになります。まず利用する変数を用意します。

```dart
class _MyHomePageState extends State<MyHomePage> {
  var listItem = [];           // 記事一覧が入る
  var _selectedIndexValue = 0; // 記事一覧か、お気に入り登録記事表示の切り替え用
}
```

そしてクラスの初期化時に、ニュース記事を取得し、その結果をlistItemに入れます。ニュース記事は日付の降順で並べて、前記事を取得しています。データストアを検索する際にはNCMBQueryクラスを使います。

```dart
@override
void initState() {
	super.initState();
	getNews();
}

// ニュースを取得する処理
getNews() async {
	// 匿名認証実行
	await login();
	// ニュース記事が入っているクラスを指定
	var entry = NCMBQuery('Entry');
	// 登録日時の降順
	entry.order('createDate');
	// データ取得
	var ary = await entry.fetchAll();
	// 結果をlistItemに適用
	setState(() {
		listItem = ary;
	});
}
```

login関数は、匿名認証を使って認証を行っています。セッションはローカルデータに保存されて、次回からそのデータを復元する形になります。そのため、セッションの有効性は確認していません。そこで、復元した際にデータストアにアクセスして、セッションの有効性を確認しています。もし有効でない場合には、元々のUUIDを使って再度匿名認証を実行しています。

```dart
login() async {
	// 現在ログインしているユーザデータを取得
	var user = await NCMBUser.currentUser();
	// nullの場合はログインしていない
	if (user == null) {
		// 匿名認証実行
		await NCMBUser.loginAsAnonymous();
	// ログインしている場合はセッションの有効性をチェック
	} else if (!(await user.enableSession())) {
		// セッションが無効だった場合
		// 保存されている認証データを取得
		var authData = user.get('authData') as Map;
		// ログアウトを実行
		await NCMBUser.logout();
		// 再度同じUUIDを使って匿名認証を実行
		await NCMBUser.loginAsAnonymous(id: authData['anonymous']['id']);
	}
}
```

### 表示処理

![localhost_53010_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/28e31e61-5095-2491-ec73-b7633c7bb133.png)


表示処理を行う画面の内容です。画面上部にはCupertinoSegmentedControlを使って、記事一覧とお気に入り表示を切り替えできるようにしています。記事一覧はListViewを使って表示しています。記事一覧をタップした際にはMaterialPageRouteを使ってMyDetailPageへ遷移します。

```dart
@override
Widget build(BuildContext context) {
	return Scaffold(
			appBar: AppBar(title: Text(widget.title)),
			body: Column(
					mainAxisAlignment: MainAxisAlignment.spaceEvenly,
					children: <Widget>[
						CupertinoSegmentedControl(
							children: const {
								0: Text('All entries'),
								1: Text('Favorited'),
							},
							groupValue: _selectedIndexValue,
							onValueChanged: (value) async {
								await changeList(value);
							},
						),
						Expanded(
								child: ListView.builder(
							itemBuilder: (BuildContext context, int index) {
								var item = listItem[index];
								return Container(
										decoration: const BoxDecoration(
											border: Border(
												bottom: BorderSide(color: Colors.black38),
											),
										),
										child: ListTile(
											title: Text(item.get('title')),
											subtitle: Text(stripTags(item.get('description')),
													overflow: TextOverflow.ellipsis, maxLines: 2),
											onTap: () {
												Navigator.push(
														context,
														MaterialPageRoute<void>(
																settings:
																		const RouteSettings(name: '/detail'),
																builder: (BuildContext context) =>
																		MyDetailPage(object: item)));
											},
										));
							},
							itemCount: listItem.length,
						))
					]));
}
```

ここまでで一覧表示機能ができあがります。

## まとめ

今回はニュース記事の取得にスクリプトを利用し、取り込んだ記事の表示を行いました。Flutter SDKとしては、記事の表示のみなのでNCMBQueryクラスを使えば簡単にできます。次回は[記事の詳細表示（ここはNCMBは使っていません）に加えて、お気に入り登録について解説](https://qiita.com/goofmint/items/3a29cd05dd5d0965a5b0)します。