アプリ内WebViewの実装
前回にてRssFeedで引っ張ってきた要素をリストで表示することができた。
そのそれぞれのリストタイルをタップすることで、RssItemのリンクURLサイトをアプリ内webviewにて表示させていく。
webviewを実装していくにあたり、使えるパッケージが2点あった。
・webview_flutter
・flutter_inappwebview
webview_flutter
はシンプルなもの、flutter_inappwebview
はより複雑なものを作れる、といったような違いだそう。
詳しい違いについては下記サイトにて紹介されている。
今回はwebview_flutter
で充分そうなのでこちらを使う。
コーディング前に使い方を調べていると、割と最近かなり大きなバージョンのアップデートがされていたようで、バージョン4.0以上は使用感がガラッと変わる。
以下サイトにてわかりやすく解説されているので大いに参考にする。
見様見真似で書いていく。
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewPage extends StatefulWidget {
const WebViewPage({Key? key, required this.rssUrl}) : super(key: key);
final String rssUrl;
@override
State<WebViewPage> createState() => _WebViewPageState();
}
class _WebViewPageState extends State<WebViewPage> {
late final WebViewController _controller;
bool _isLoading = false;
@override
void initState() {
super.initState();
_controller = WebViewController()
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
setState(() {
_isLoading = true;
});
},
onPageFinished: (String url) {
setState(() {
_isLoading = false;
});
},
),
)
..loadRequest(Uri.parse(widget.rssUrl));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
//閉じるボタン
leading: IconButton(
onPressed: (){ Navigator.pop(context);} ,
icon: const Icon(Icons.clear)
),
title: const Text('イベント詳細'),
actions: [
//戻るボタン
IconButton(
onPressed: () async {
_controller.goBack();
},
icon: const Icon(
Icons.arrow_back,
),
),
//進むボタン
IconButton(
onPressed: () async {
_controller.goForward();
},
icon: const Icon(
Icons.arrow_forward,
),
)
],
),
body: Column(
children: [
if (_isLoading) const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: CircularProgressIndicator(),
),
),
Expanded(child: WebViewWidget(controller: _controller)),
],
),
);
}
}
import 'package:eve_search/web_view_page.dart';
onTap:(){
setState(() {
Navigator.push(context, MaterialPageRoute(builder: (context)
=> WebViewPage(rssUrl: onlyHiphopList[index].linkUrl)));
});
},
遷移やAppbarの各ボタンは問題なく動いたが、サムネイルにも利用したOGP画像が表示されない不具合が起きた。
以下を追加することで改善された。
@override
void initState() {
super.initState();
_controller = WebViewController()
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (String url) {
setState(() {
_isLoading = true;
});
},
onPageFinished: (String url) {
setState(() {
_isLoading = false;
});
},
),
)
..loadRequest(Uri.parse(widget.rssUrl));
}
エリア毎の絞り込み機能の実装
#2にて用意した選択肢を選ぶshowModalBottomSheetのアクションを実装していく。
今回利用しているRssFeedは各地方、各都道府県毎にそれぞれRssが用意されていたので使っていく。
各地方はそれぞれ個別に書き、都道府県はswitch文でタップ時渡す値を条件分けしていく。
import 'package:flutter/material.dart';
import 'const.dart';
class PrefecturesWindow extends StatefulWidget {
const PrefecturesWindow({super.key});
@override
State<PrefecturesWindow> createState() => _PrefecturesWindowState();
}
class _PrefecturesWindowState extends State<PrefecturesWindow> {
void selectPrefecture(String prefecture){
switch(prefecture){
case '青森県':
Navigator.pop(context, [' > 東北 > $prefecture', 'https://iflyer.tv/rss/events/tohoku/aomori/']);
break;
case '岩手県':
Navigator.pop(context, [' > 東北 > $prefecture', 'https://iflyer.tv/rss/events/tohoku/iwate/']);
break;
//省略(実際は47都道府県分)
case '鹿児島県':
Navigator.pop(context, [' > 九州 > $prefecture', 'https://iflyer.tv/rss/events/kyushu/kagoshima/']);
break;
case '沖縄県':
Navigator.pop(context, [' > 九州 > $prefecture', 'https://iflyer.tv/rss/events/kyushu/okinawa/']);
break;
default:
errorDialog();
break;
}
}
void errorDialog(){
showDialog<void>(
context: context,
builder: (_){
return AlertDialog(
title: Column(
children: [
const Text('エラーが発生しました'),
const Text('もう一度お試しください'),
const SizedBox(height: 8,),
ElevatedButton(
child: const Text('OK'),
onPressed: (){
Navigator.pop(context,);
},
)
],
),
);
}
);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.of(context).size.width*0.95,
height: MediaQuery.of(context).size.height*0.95,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 16,),
ListTile(
title: const Text('全国'),
trailing: const Icon(
Icons.keyboard_arrow_right,
),
onTap: () {
setState(() {
Navigator.pop(context, ['', 'https://iflyer.tv/rss/events/']);
});
},
),
ListTile(
title: const Text('北海道'),
trailing: const Icon(
Icons.keyboard_arrow_right,
),
onTap: () {
setState(() {
Navigator.pop(context, [' > 北海道', 'https://iflyer.tv/rss/events/hokkaido/']);
});
},
),
ExpansionTile(
title: const Text('東北'),
children: [
Column(
children: [
const Divider(
height: 1,
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: ListTile(
title: const Text('東北全域'),
trailing: const Icon(
Icons.keyboard_arrow_right,
),
onTap: (){
setState(() {
Navigator.pop(context, [' > 東北全域', 'https://iflyer.tv/rss/events/tohoku/']);
});
},
),
),
Column(
children:
touhokuList.map((e) => Column(
children: [
const Divider(
height: 1,
),
Padding(
padding: const EdgeInsets.only(left: 16.0),
child: ListTile(
title: Text(e),
trailing: const Icon(
Icons.keyboard_arrow_right,
),
onTap: (){
setState(() {
selectPrefecture(e);
});
},
),
),
],
)).toList(),
),
],
)
],
),
//以下省略
],
),
),
),
);
}
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _selectArea = ['', 'https://iflyer.tv/rss/events'];
List<Rss> onlyHiphopList = [];
String areaUrl = '';
Future<List<Rss>> fetchFeed() async {
final response = await http
.get(Uri.parse(_selectArea[1]));
if (response.statusCode != 200) {
throw Exception('Failed to fetch');
}
//省略
ElevatedButton(
onPressed: () async{
- String selectArea = await showModalBottomSheet(
+ List<String> selectArea = await showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(10)),
),
builder: (BuildContext context) {
return const PrefecturesWindow();
},
);
setState(() {
_selectArea = selectArea;
});
},
child: const Text('エリアを選択する',),
),
これにて最低限実装したい機能は問題なく積み込めた。
最後に全体的にレイアウトをいい感じにしたのがこちら↓
一応コーディングに関してはここでひと段落として、あとはテスト配信とリリースだ。
テスト配信の前か後かどちらになるか未定だが、あまり重たくない追加機能の検討、実装もしていくつもりなので、もしあれば引き続きこちらで記録していこうと思う。
今回はここまで。