Flutter, React Native(Expo), Xamarin.Formsでボタンを押したらModal表示する実装を比べる

Flutter, React Native(Expo), Xamarin.Formsで、振る舞いが同じアプリを作っているんですが、その時にそれぞれのフレームワークで、Modalの実装はどうなるのか調べました。今回はボタンを押したらModalが表示する実装にしました。
調べた時に「要点となる実装部分だけの記載」という記事が多くて、初心者にとっては実装する時に「このコードはどこに追加(置き換え)するんだろうか？」って思ったので、FlutterとReact Native(expo)はコピペで動作するように記載しました。Xamarin.Formsはコピペの後でnamespaceを書き換えてください。

# 留意事項
## その１
「こう実装すればこう動く、ってことが分かればそれでOK！」を方針に実装を進めた個人開発の内容を記載しましたので、「なぜその実装が必要なのか？」や「なぜその実装で動くのか？」は分かってない部分多数です。。。 :bow:

## その２
筆者は実戦経験が乏しい（というより、アプリ開発は個人開発を除いて、現場での開発が未経験。。）、なので、ご指摘は「ここは、こうした方が良いよ:wink:」くらいのノリでお願いします。:pray::pray::pray:

## その３
「HelloWorldはできたけど、他にはこんなことできたら嬉しいな〜」って思っていた時のことを思い出して書きました。そのため、強々エンジニアや超人エンジニアの方々はこのページを参考にしない方が良いと思います。:expressionless::sleepy: :cold_sweat:

※iOSとAndroidのバージョン
![iOS-13.3](https://img.shields.io/badge/iOS-13.3-brightgreen)![Android-29(API Level)](https://img.shields.io/badge/Android-29(API Level)-brightgreen)

# Flutter

以下の記事を参考に、FlutterでModal(Dialog)の実装を行いました。
参照記事：https://stackoverflow.com/questions/60047676/flutter-display-photo-or-image-as-modal-popup

## 概要

ボタンを押したらshowDialogを呼んで、showDialogからModalを呼び出します。（これだけです！）

## 実装
![Flutter-1.20.3](https://img.shields.io/badge/Flutter-1.20.3-brightgreen)

```dart:main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            title: Text('Image Popup example')
        ),
        backgroundColor: Colors.grey[800],
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              FlatButton(
                onPressed:  () async {
                  await showDialog(
                      context: context,
                      builder: (_) => ImageDialog()
                  );
                },
                color: Colors.blue,
                child: Text(
                  'Open Modal',
                  style: TextStyle(
                      color:Colors.white,
                      fontSize: 20.0
                  ),
                ),
              ),
            ],
          ),
        )
    );
  }
}

class ImageDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Dialog(
      child: Image.network(
        'https://picsum.photos/250?image=9',
        width: 500,
        height: 500,
      ),
    );
  }
}
```

# React Native(Expo)
以下の記事を参考に、React Native(Expo)でModal(Dialog)の実装を行いました。

参照記事：https://reactnative.dev/docs/modal

## 概要

React Nativeで用意されているModalを利用するんですが、Modalを閉じる場合にModalの枠外をタップしてもModalが閉じないため、Modalに閉じるボタンを実装しました。

## 実装
![React Native cli-2.0.1](https://img.shields.io/badge/React Native cli-2.0.1-brightgreen)![Expo-3.23.3](https://img.shields.io/badge/Expo-3.23.3-brightgreen)

```javascript:App.js
import React, { useState } from "react";
import { Modal, StyleSheet, Text, TouchableHighlight, View, Image } from "react-native";

export default function App() {

  const [modalVisible, setModalVisible] = useState(false);

  return (
    <View style={styles.centeredView}>
      <Modal
        animationType="slide"
        transparent={true}
        visible={modalVisible}
      >
        <View style={styles.centeredView}>
          <View style={styles.modalView}>
            <Image style={styles.image}
              source={{uri: 'https://picsum.photos/250?image=9'}}
            />

            <TouchableHighlight
              style={{ ...styles.openButton, backgroundColor: "#2196F3" }}
              onPress={() => {
                setModalVisible(!modalVisible);
              }}
            >
              <Text style={styles.textStyle}>Hide Modal</Text>
            </TouchableHighlight>
          </View>
        </View>
      </Modal>

      <TouchableHighlight
        style={styles.openButton}
        onPress={() => {
          setModalVisible(true);
        }}
      >
        <Text style={styles.textStyle}>Open Modal</Text>
      </TouchableHighlight>
    </View>
  );
}

const styles = StyleSheet.create({
  centeredView: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    marginTop: 22
  },
  modalView: {
    width: 300,
    height: 200,
    margin: 20,
    backgroundColor: "white",
    borderRadius: 20,
    padding: 35,
    alignItems: "center",
    shadowColor: "#000",
    shadowOffset: {
      width: 0,
      height: 2
    },
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
    elevation: 5
  },
  openButton: {
    backgroundColor: "#F194FF",
    padding: 10,
    elevation: 2
  },
  textStyle: {
    color: "white",
    fontWeight: "bold",
    textAlign: "center"
  },
  modalText: {
    marginBottom: 15,
    textAlign: "center"
  },
  image: {
    width: 400,
    height: 300,
    marginLeft: 10,
    marginTop: 10,
  }
});
```

# Xamarin.Forms
以下の記事を参考に、Xamarin.FormsでModal(Dialog)の実装を行いました。

参照記事：https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/app-fundamentals/navigation/modal

## 概要

ボタンを押した時に別Class(ModalPage.xaml, ModalPage.xaml.cs)としてModalを呼び出す方法で実装しました。また、Modalを閉じる場合にModalの枠外をタップしてもModalが閉じないため、Modalに閉じるボタンを実装しました。

## 実装
![Xamarin.Forms-4.6.0.1141](https://img.shields.io/badge/Xamarin.Forms-4.6.0.1141-brightgreen)

```xml:ModalPage.xaml.cs
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="xamarin.forms_project.ModalPage">
    <StackLayout x:Name="layout">
        <Image Source="https://picsum.photos/250?image=9" />
        <Button x:Name="closeButton" Command="{Binding PopupCommand}" Text="Close Modal" Clicked="OnClicked" />
    </StackLayout>
</ContentPage>
```

```C#:ModalPage.xaml.cs
using System;
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class ModalPage : ContentPage
    {
        public ModalPage()
        {
            InitializeComponent();
        }

        async void OnClicked(object sender, EventArgs e)
        {
            await Navigation.PopModalAsync();
        }
    }
}
```

MainPage.xamlに以下を記述します。

```xml:MainPage.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="xamarin.forms_project.MainPage">
    <StackLayout>
        <Button x:Name="button" Command="{Binding PopupCommand}" Text="Open Modal" Clicked="OnSelected" />
    </StackLayout>
</ContentPage>
```

```C#:MainPage.xaml.cs
using System;
using Xamarin.Forms;

namespace xamarin.forms_project
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        async void OnSelected(object sender, EventArgs e)
        {
            await Navigation.PushModalAsync(new ModalPage());
        }
    }
}
```

# 気になったこと
Xamarin.Formsで「Rg.Plugins.Popup」を入れた時にModal + Imageでは動作するんですが、modalにscrollviewやpinch-in/outを実装するとなぜか動作しませんでした。。:sweat:
（動作させる方法をご存知の方、教えてくださいっっっっ！！）