ソースコード source code

下記アプリの主要なソースコードを公開しています。アプリ開発の参考になれば幸いです。

画像等が別途必要ですので下記情報のみでアプリが完成するものではありません。 アプリは少しずつ機能拡張していますのでストア公開されているアプリと内容が異なる場合があります。 コードはコピーして自由にお使いいただけます。ただし著作権は放棄しておりませんので全部の再掲載はご遠慮ください。部分的に再掲載したり、改変して再掲載するのは構いません。 自身のアプリ作成の参考として個人使用・商用問わず自由にお使いいただけます。 コード記述のお手本を示すものではありません。ミニアプリですので変数名などさほど気遣いしていない部分も有りますし間違いも有るかと思いますので参考程度にお考え下さい。 他の賢者の皆様が公開されているコードを参考にした箇所も含まれます。Flutter開発の熟練者が書いたコードではありません。 エンジニア向け技術情報共有サービスではありませんので説明は省いています。 GitHubなどへの公開は予定しておりません。

下記コードの最終ビルド日: 2025-09-26

pubspec.yaml

name: lunarage
description: "Lunar Age"
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.8+9

environment:
  sdk: ^3.6.0-198.0.dev

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.8
  webview_flutter: ^4.10.0
  webview_flutter_android: any
  webview_flutter_wkwebview: any
  google_mobile_ads: ^6.0.0
  package_info_plus: ^9.0.0
  path_provider: ^2.0.11

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_launcher_icons: ^0.14.3    #flutter pub run flutter_launcher_icons
  flutter_native_splash: ^2.4.4     #flutter pub run flutter_native_splash:create

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^6.0.0

flutter_launcher_icons:
  android: "launcher_icon"
  ios: true
  image_path: "assets/icon/icon.png"
  adaptive_icon_background: "assets/icon/icon_back.png"
  adaptive_icon_foreground: "assets/icon/icon_fore.png"

flutter_native_splash:
  color: '#000000'
  image: 'assets/image/splash.png'
  color_dark: '#000000'
  image_dark: 'assets/image/splash.png'
  fullscreen: true
  android_12:
    icon_background_color: '#000000'
    image: 'assets/image/splash.png'
    icon_background_color_dark: '#000000'
    image_dark: 'assets/image/splash.png'

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
    - assets/icon/
    - assets/image/
    - assets/httpdocs/
    - assets/httpdocs/common/css/
    - assets/httpdocs/image/
    - assets/httpdocs/js/

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/to/resolution-aware-images

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/to/asset-from-package

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/to/font-from-package

lib/ad_banner_widget.dart

import 'package:flutter/cupertino.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'package:lunarage/ad_manager.dart';

class AdBannerWidget extends StatefulWidget {
  final AdManager adManager;
  const AdBannerWidget({super.key, required this.adManager});
  @override
  State<AdBannerWidget> createState() => _AdBannerWidgetState();
}

class _AdBannerWidgetState extends State<AdBannerWidget> {
  int _lastBannerWidthDp = 0;
  bool _isAdLoaded = false;
  bool _isLoading = false;
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: LayoutBuilder(
        builder: (context, constraints) {
          final int width = constraints.maxWidth.isFinite
              ? constraints.maxWidth.truncate()
              : MediaQuery.of(context).size.width.truncate();
          final bannerAd = widget.adManager.bannerAd;
          if (width > 0) {
            WidgetsBinding.instance.addPostFrameCallback((_) {
              if (mounted) {
                final bannerAd = widget.adManager.bannerAd;
                final bool widthChanged = _lastBannerWidthDp != width;
                final bool sizeMismatch =
                    bannerAd == null || bannerAd.size.width != width;
                if ((widthChanged || !_isAdLoaded || sizeMismatch) &&
                    !_isLoading) {
                  _lastBannerWidthDp = width;
                  setState(() {
                    _isAdLoaded = false;
                    _isLoading = true;
                  });
                  widget.adManager.loadAdaptiveBannerAd(width, () {
                    if (mounted) {
                      setState(() {
                        _isAdLoaded = true;
                        _isLoading = false;
                      });
                    }
                  });
                }
              }
            });
          }
          if (_isAdLoaded && bannerAd != null) {
            return Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                SizedBox(height: 10),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    SizedBox(
                      width: bannerAd.size.width.toDouble(),
                      height: bannerAd.size.height.toDouble(),
                      child: AdWidget(ad: bannerAd),
                    ),
                  ],
                )
              ]
            );
          } else {
            return const SizedBox.shrink();
          }
        },
      ),
    );
  }
}

lib/ad_manager.dart

import 'dart:async';
import 'dart:io' show Platform;
import 'dart:ui';
import 'package:google_mobile_ads/google_mobile_ads.dart';

class AdManager {
  // Test IDs
  // static const String _androidAdUnitId = "ca-app-pub-3940256099942544/6300978111";
  // static const String _iosAdUnitId     = "ca-app-pub-3940256099942544/2934735716";

  // Production IDs
  static const String _androidAdUnitId = "ca-app-pub-0/0";
  static const String _iosAdUnitId     = "ca-app-pub-0/0";

  static String get _adUnitId =>
      Platform.isIOS ? _iosAdUnitId : _androidAdUnitId;

  BannerAd? _bannerAd;
  int _lastWidthPx = 0;
  VoidCallback? _onLoadedCb;
  Timer? _retryTimer;
  int _retryAttempt = 0;

  BannerAd? get bannerAd => _bannerAd;

  Future<void> loadAdaptiveBannerAd(
    int widthPx,
    VoidCallback onAdLoaded,
  ) async {
    _onLoadedCb = onAdLoaded;
    _lastWidthPx = widthPx;
    _retryAttempt = 0;
    _retryTimer?.cancel();
    _startLoad(widthPx);
  }

  Future<void> _startLoad(int widthPx) async {
    _bannerAd?.dispose();

    AnchoredAdaptiveBannerAdSize? adaptiveSize;
    try {
      adaptiveSize =
          await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
            widthPx,
          );
    } catch (_) {
      adaptiveSize = null;
    }
    final AdSize size = adaptiveSize ?? AdSize.fullBanner;

    _bannerAd = BannerAd(
      adUnitId: _adUnitId,
      request: const AdRequest(),
      size: size,
      listener: BannerAdListener(
        onAdLoaded: (ad) {
          _retryTimer?.cancel();
          _retryAttempt = 0;
          final cb = _onLoadedCb;
          if (cb != null) {
            cb();
          }
        },
        onAdFailedToLoad: (ad, err) {
          ad.dispose();
          _scheduleRetry();
        },
      ),
    )..load();
  }

  void _scheduleRetry() {
    _retryTimer?.cancel();
    // Exponential backoff: 3s, 6s, 12s, max 30s
    _retryAttempt = (_retryAttempt + 1).clamp(1, 5);
    final seconds = _retryAttempt >= 4 ? 30 : (3 << (_retryAttempt - 1));
    _retryTimer = Timer(Duration(seconds: seconds), () {
      _startLoad(_lastWidthPx > 0 ? _lastWidthPx : 320);
    });
  }

  void dispose() {
    _bannerAd?.dispose();
    _retryTimer?.cancel();
  }
}

lib/const_value.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///
library;

import 'package:flutter/material.dart';

class ConstValue {
  //color
  static const Color colorHeader = Color.fromRGBO(0,0,0,0.1);
  static const Color colorText = Color.fromRGBO(255,255,255,1);
  static const Color colorBack = Color.fromRGBO(0,0,0,1);
  static const Color colorSettingHeader = Color.fromRGBO(69, 99, 202, 1.0);
  static const Color colorUiActiveColor = Color.fromRGBO(69, 99, 202, 1.0);
  static const Color colorUiInactiveColor = Colors.black26;

}

lib/local_server.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2025-02-20
///
library;

import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';

class LocalServer {
  late HttpServer _server;
  String _serverUrl = '';
  final Map<String, Uint8List> _cache = {};
  final Map<String, ContentType> _contentTypeCache = {};

  LocalServer() { //constructor
  }

  Future<void> start() async {
    final Completer<void> completer = Completer<void>();
    _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 8080);
    _serverUrl = 'http://${_server.address.host}:${_server.port}/';
    _server.listen((HttpRequest request) async {
      final pathSegments = request.uri.pathSegments;
      final String rawPath = pathSegments.join('/');
      final String pathWithoutQuery = rawPath.split('?')[0];
      final String path = pathWithoutQuery.isEmpty ? 'assets/httpdocs/index.html' : 'assets/httpdocs/$pathWithoutQuery';

      try {
        // Check cache first
        if (_cache.containsKey(path)) {
          request.response.headers.contentType = _contentTypeCache[path];
          request.response.headers.set(HttpHeaders.cacheControlHeader, 'public, max-age=31536000'); // Cache for 1 year
          request.response.add(_cache[path]!);
          await request.response.close();
          return;
        }

        ContentType contentType;
        if (path.endsWith('.html')) {
          contentType = ContentType.html;
        } else if (path.endsWith('.css')) {
          contentType = ContentType('text', 'css');
        } else if (path.endsWith('.js')) {
          contentType = ContentType('application', 'javascript');
        } else if (path.endsWith('.webp')) {
          contentType = ContentType('image', 'webp');
        } else if (path.endsWith('.ico')) {
          contentType = ContentType('image', 'icon');
        } else {
          contentType = ContentType('application', 'octet-stream');
        }

        final ByteData data = await rootBundle.load(path);
        final Uint8List bytes = data.buffer.asUint8List();

        // Store in cache
        _cache[path] = bytes;
        _contentTypeCache[path] = contentType;

        request.response.headers.contentType = contentType;
        request.response.headers.set(HttpHeaders.cacheControlHeader, 'public, max-age=31536000'); // Cache for 1 year
        request.response.add(bytes);
        await request.response.close();
      } catch (e) {
        request.response.statusCode = HttpStatus.notFound;
        request.response.write('404 Not Found');
        await request.response.close();
      }
    });
    completer.complete();
    return completer.future;
  }

  void close() {
    _server.close();
  }

  String url() {
    return _serverUrl;
  }
}

lib/main.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2025-02-17
///
library;

import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:webview_flutter/webview_flutter.dart';

import 'package:lunarage/const_value.dart';
import 'package:lunarage/version_state.dart';
import 'package:lunarage/setting.dart';
import 'package:lunarage/ad_manager.dart';
import 'package:lunarage/ad_banner_widget.dart';
import 'package:lunarage/my_web_view_controller.dart';
import 'package:lunarage/local_server.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MainHomePage(),
    );
  }
}

class MainHomePage extends StatefulWidget {
  const MainHomePage({super.key});
  @override
  State<MainHomePage> createState() => _MainHomePageState();
}

class _MainHomePageState extends State<MainHomePage> {
  late AdManager _adManager;
  final MyWebViewController _myWebViewController = MyWebViewController(); //WebViewController
  final LocalServer _localServer = LocalServer(); //ローカルサーバー
  late final WebViewController _webViewController;
  late final List<int> _yearList;
  late final List<DropdownMenuItem<int>> _yearDropdownItems;
  final List<int> _monthList = List<int>.generate(12, (index) => index + 1);
  late final List<DropdownMenuItem<int>> _monthDropdownItems;
  int _selectedYear = DateTime.now().year;
  int _selectedMonth = DateTime.now().month;

  //アプリのバージョン取得
  void _getVersion() async {
    PackageInfo packageInfo = await PackageInfo.fromPlatform();
    setState(() {
      VersionState.versionSave(packageInfo.version);
    });
  }

  void _updateWebView() async {
    String serverUrl = _localServer.url();
    await _webViewController.loadRequest(Uri.parse('${serverUrl}index.html?year=$_selectedYear&month=$_selectedMonth'));
  }

  List<int> _generateYearList() {
    int currentYear = DateTime.now().year;
    return List<int>.generate(161, (index) => currentYear - 80 + index);
  }
  void _incrementYear() {
    setState(() {
      if (_selectedYear < _yearList.last) {
        _selectedYear += 1;
      }
      _updateWebView();
    });
  }
  void _decrementYear() {
    setState(() {
      if (_selectedYear > _yearList.first) {
        _selectedYear -= 1;
      }
      _updateWebView();
    });
  }
  void _incrementMonth() {
    setState(() {
      if (_selectedMonth < 12) {
        _selectedMonth += 1;
      } else {
        if (_selectedYear < _yearList.last) {
          _selectedMonth = 1;
          _selectedYear += 1;
        }
      }
      _updateWebView();
    });
  }
  void _decrementMonth() {
    setState(() {
      if (_selectedMonth > 1) {
        _selectedMonth -= 1;
      } else {
        if (_selectedYear > _yearList.first) {
          _selectedMonth = 12;
          _selectedYear -= 1;
        }
      }
      _updateWebView();
    });
  }

  //ページ起動開始時に一度だけ呼ばれる
  @override
  void initState() {
    super.initState();
    _adManager = AdManager();
    _yearList = _generateYearList();
    _yearDropdownItems = _yearList.map<DropdownMenuItem<int>>((int year) {
      return DropdownMenuItem<int>(
        value: year,
        child: Text(year.toString(),
            style: const TextStyle(
              color: ConstValue.colorText,
            )
        ),
      );
    }).toList();
    _monthDropdownItems = _monthList.map<DropdownMenuItem<int>>((int month) {
      return DropdownMenuItem<int>(
        value: month,
        child: Text(
          month.toString(),
          style: const TextStyle(color: Colors.white), // テキストの色を設定
        ),
      );
    }).toList();
    _webViewController = _myWebViewController.controller();
    _initApp();
  }

  // アプリの非同期初期化処理
  Future<void> _initApp() async {
    _getVersion();
    // ローカルサーバーが起動してからWebViewをロードする
    await _localServer.start();
    _updateWebView();
  }
  //ページ終了時に一度だけ呼ばれる
  @override
  void dispose() {
    _adManager.dispose();
    _localServer.close();
    super.dispose();
  }
  //画面全体
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      appBar: AppBar(
        backgroundColor: ConstValue.colorHeader,
        //タイトル表示
        title: const Text('Lunar age',
          style: TextStyle(
            color: Colors.white,
            fontSize: 15.0,
          )
        ),
        //設定ボタン
        actions: <Widget>[
          TextButton(
            onPressed: () async {
              bool? ret = await Navigator.of(context).push(
                MaterialPageRoute<bool>(builder:(context) => const SettingPage()),
              );
              //awaitで呼び出しているので、settingから戻ったら以下が実行される。
              if (ret!) { //設定で適用だった場合
                setState(() {});
              }
            },
            child: Text('Information',
              style: const TextStyle(
                color: Colors.white,
              )
            )
          )
        ]
      ),
      body: SafeArea(
        child: Column(children:[
          Row(children:[
            Expanded(child: _selectYear()),
            Expanded(child: _selectMonth()),
          ]),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.only(top: 10, left: 10, right: 10, bottom: 0),
              child: Center(
                child: WebViewWidget(controller: _webViewController),
              ),
            )
          ),
        ])
      ),
      bottomNavigationBar: AdBannerWidget(adManager: _adManager),
    );
  }

  Widget _selectYear() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: Icon(Icons.chevron_left),
          color: Colors.white,
          onPressed: _decrementYear,
        ),
        DropdownButton<int>(
          value: _selectedYear,
          onChanged: (int? newValue) {
            setState(() {
              _selectedYear = newValue!;
              _updateWebView();
            });
          },
          dropdownColor: Colors.grey[850],
          items: _yearDropdownItems,
        ),
        IconButton(
          icon: Icon(Icons.chevron_right),
          color: Colors.white,
          onPressed: _incrementYear,
        ),
      ],
    );
  }

  Widget _selectMonth() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: Icon(Icons.chevron_left),
          color: Colors.white,
          onPressed: _decrementMonth,
        ),
        DropdownButton<int>(
          value: _selectedMonth,
          onChanged: (int? newValue) {
            setState(() {
              _selectedMonth = newValue!;
              _updateWebView();
            });
          },
          dropdownColor: Colors.grey[850],  // ドロップダウンの背景色を設定
          items: _monthDropdownItems,
        ),
        IconButton(
          icon: Icon(Icons.chevron_right),
          color: Colors.white,
          onPressed: _incrementMonth,
        ),
      ],
    );
  }
}

lib/my_web_view_controller.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2025-02-20
///
library;

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';

class MyWebViewController {
  MyWebViewController() {
    //constructor
  }
  WebViewController controller() {
    late final PlatformWebViewControllerCreationParams params;
    if (WebViewPlatform.instance is WebKitWebViewPlatform) {
      params = WebKitWebViewControllerCreationParams(
        allowsInlineMediaPlayback: true,
        mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},
      );
    } else {
      params = const PlatformWebViewControllerCreationParams();
    }
    final WebViewController controller = WebViewController.fromPlatformCreationParams(params);
    controller
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onUrlChange: (UrlChange change) {
            //debugPrint('url change to ${change.url}');
          },
        ),
      );
    controller.setBackgroundColor(const Color(0x00000000));
    if (controller.platform is AndroidWebViewController) {
      AndroidWebViewController.enableDebugging(true);
      (controller.platform as AndroidWebViewController).setMediaPlaybackRequiresUserGesture(false);
    }
    return controller;
  }
}

lib/setting.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-10-15
///
library;

import 'package:flutter/material.dart';

import 'package:lunarage/version_state.dart';
import 'package:lunarage/ad_manager.dart';
import 'package:lunarage/ad_banner_widget.dart';

class SettingPage extends StatefulWidget {
  const SettingPage({super.key});
  @override
  State<SettingPage> createState() => _SettingPageState();
}

class _SettingPageState extends State<SettingPage> {
  late AdManager _adManager;
  //ページ起動時に一度だけ実行される
  @override
  void initState() {
    super.initState();
    _adManager = AdManager();
  }
  //ページ終了時に一度だけ実行される
  @override
  void dispose() {
    _adManager.dispose();
    super.dispose();
  }
  //ページ描画
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).colorScheme.surface,
      appBar: AppBar(
        centerTitle: true,
        elevation: 0,
        //設定キャンセルボタン
        leading: IconButton(
          icon: const Icon(Icons.close),
          onPressed: () {
            Navigator.of(context).pop(false); //falseを返す
          },
        ),
        title: Text(
          'Information',
          style: TextStyle(
            color: Theme.of(context).colorScheme.onSurface,
          ),
        ),
        foregroundColor: Theme.of(context).colorScheme.onSurface,
        actions: [
          //設定OKボタン
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: () async {
              if (!mounted) {
                return;
              }
              Navigator.of(context).pop(true);  //trueを返す
            },
          ),
        ],
      ),
      body: SafeArea(
        child: Column(children:[
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(20),
              child: Column(children: [
                Padding(
                  padding: const EdgeInsets.only(top: 24, left: 0, right: 0, bottom: 36),
                  child: SizedBox(
                    child: Text('version  ${VersionState.versionLoad()}',
                      style: TextStyle(
                        fontSize: 10,
                        color: Theme.of(context).colorScheme.onSurface,
                      ),
                    ),
                  ),
                ),
              ]),
            ),
          ),
        ]),
      ),
      bottomNavigationBar: AdBannerWidget(adManager: _adManager),
    );
  }
}

lib/version_state.dart

///
/// @author akira ohmachi
/// @copyright ao-system, Inc.
/// @date 2023-01-27
///
library;

class VersionState {

  static String _version = '';

  //バージョンを記録
  static void versionSave(String str) {
    _version = str;
  }
  //バージョンを返す
  static String versionLoad() {
    return _version;
  }

}