導入・挨拶
こんにちは!レアゾンホールディングス25卒エンジニアの中嶋蓮と申します。
今回は、ネイティブアプリ研修について、ご紹介させていただきます。
研修の概要
今回の研修は、まずDartとFlutterの基礎を学び、その後のハンズオンで実際にネイティブアプリを開発するところまでを5日間で行いました。
研修の詳細
ここからは5日間の研修で行った内容をご紹介します。
研修1日目
1日目は以下の内容を講義形式で学びました。
- 環境構築
- Dart、Flutterとは何か?
- Dartのハンズオン
DartとFlutterの講義では、Dart、Flutterの概要、Dartの基礎文法や、クラス定義、継承について学びました。
その後は、運営の方々が作成した基礎文法とクラスの問題を解いたり、HTTPリクエストを送ったりとハンズオン形式で講義が進みました。実際に手を動かすことで知識の定着に繋がったのですごくありがたかったです。
この日学んだDartとFlutterについて、以下でご説明します。
Dartとは?
DartとはGoogleが開発したオープンソースのプログラミング言語です。
主な特徴としては以下の通りです
- クラスベースのオブジェクト指向言語
- 静的型付け言語
- Null安全(nullを許容する型には?をつけ、明示が必須)
- 非同期処理の仕組みがJavaScriptに似ていて使いやすい
Flutterとは?
Flutterとは、Googleが提供するオープンソースのDartフレームワークです。
主な特徴としては以下の通りです。
- 開発者体験が良い
- ホットリロード対応
- マルチプラットフォーム対応
- テストの標準搭載
- 高速な実行速度
- 宣言的UI構築が可能
- Widgetを使用することで宣言的にUIを構築することが可能(Widgetについては後述)
研修2日目
2日目はFlutterについて、より詳しく学びました。その中で特に焦点を当てていたのがWidgetです。ここからはWidgetについて、講義で学んだ内容をまとめたいと思います。
Widgetとは?
WidgetとはFlutterでUIを構築するための基本要素です。
例えば、Icon Widgetを使用すれば、Flutterが提供する複数の種類のアイコンから自分の求めるアイコンを画面上に表示させることができます。以下、サンプルコードです。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Single Icon Example'),
),
body: const Center(
// 以下のIcon Widgetを使用することで画面上にアイコンを表示できる
child: Icon(
Icons.android, // Androidのアイコン
size: 100.0, // アイコンのサイズを大きく設定
color: Colors.green, // アイコンの色を緑色に設定
),
),
),
);
}
}
このようなUIコンポーネントを画面に表示させるための要素がWidgetです。Flutterで実装した画面は、幾つものWidgetをツリー構造で組み合わせることで出来ています。
また、Widgetはimmutable(不変)な要素です。そのためインスタンスを一度作成したら、以降は、状態やプロパティの変更はできません。加えて、WidgetはStateless WidgetとStateful Widgetというクラスに継承されています。
Stateless Widgetとは?
Stateless Widgetとは、名前の通り、state(状態)と関連づけられていないWidgetです。(stateについては後述)
画面描画の際にWidgetを呼び出し、以降はその要素を変更しないという特性があります。
Stateful Widgetとは?
こちらも名前の通りで、stateと関連づけられているWidgetです。Stateless Widgetに対して、Stateful Widgetは、画面描画の際にWidgetの呼び出しをした以降もWidgetの内容を変更することができます。
Stateful Widgetは、createState()によってStateと関連づけられており、そのStateが変化した際にWidgetが再度レンダリングされることで、あたかもWidgetそのものが変化したように見えるWidgetです。次は度々出てくるStateについてまとめたいと思います。
Stateとは?
Stateは、ウィジェットの状態(例えば、カウントの数値やテキストの内容など)を保持するオブジェクトです。StatefulWidgetはこの状態を持ち、状態が変化すると対応するウィジェットが再構築され、画面が自動的に再描画されます。
一方、StatelessWidgetは状態を持たないため、内部のデータが変化してもウィジェットを再構築・再描画することはできません。
研修3日目
3日目は、引き続きイメージの付きづらいStateful Widgetについて、その後は今までの内容をもとに簡単なアプリの作成を行うハンズオンに取り組みました。
ここではStateful Widgetが、実際にどのようにして画面の再描画を行うのか学びました。先ほど、stateを更新すれば画面の再描画が行われると説明しましたが、厳密にはそうではありません。ボタンを押した時や、テキストエリア内の値が変更した際にsetState関数を呼び出すことで画面の再描画を行うことができます。以下サンプルコードです
// 画面再描画についてイメージするためのサンプルコード
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _counter = 0; // 状態として保持する変数
void _incrementCounter() {
_counter++;
// setStateを呼び出すことで、画面が再描画されます。
// _counter++;をsetStateのコールバック関数内に定義することも可能です。
setState(() {
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Stateful Widget Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'ボタンが押された回数:',
style: TextStyle(fontSize: 20),
),
Text(
'$_counter', // _counterの値を表示
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _incrementCounter, // ボタンが押されたら_incrementCounterを実行
child: const Text(
'カウントアップ',
style: TextStyle(fontSize: 20),
),
),
],
),
),
),
);
}
}
その後はTodoアプリと、お天気アプリの実装を通じてstateful Widgetの理解を深めました。
これら二つのアプリのハンズオンのおかげで、Widgetの使い方やstateful Widgetの理解が大きく進みました。また、この際、アプリを段階を踏みながら少しずつ作っていくという大切な事を教わりました。これは実務に入ってからもとても大事な事だと感じたので、今後も意識していきたいと思います。
研修4日目
4日目は、主に以下の内容に取り組みました。
- initState()とdispose()について学習
- チームに分かれてハードウェアの機能を使ったネイティブアプリの開発
初めに、ハードウェアの機能を活かしたサンプルアプリを通じて、initState()とdispose()について学びました。その後はチームごとにアプリの作成を行いました。
僕たちのチームは、某名探偵の蝶ネクタイをイメージしたボイスチェンジャーを作成しました。
このアプリは、その名の通り、録音した音声のピッチを変えるというアプリケーションです。
初めはライブラリを使用して、音声のピッチを変える方向で考えていたのですが、なかなか上手くいかず、最終的には、画面の傾きによって、録音した音声の速度を変えることでボイスチェンジャーを再現するようにしました。
initState()とdispose()についてまとめたいと思います。
initState()とは?
initState()は、Stateful Widgetが描画される一番最初だけ呼ばれるメソッドです。
ハードウェア機能の入力を受け付ける際などに使用します。
ハードウェア機能の入力を受け付けるインスタンスの例
- 音声入力
- センサーの入力受付
etc…
dispose()とは?
dispose()は、Widgetが使われなくなるタイミング(アプリを落とした時など)に呼ばれるメソッドです。
initState()で書いた、入力を受け付ける処理をdispose()内でキャンセルする際に使用します。
initState()で外部からの入力を受け付ける処理を書いたら、必ずセットで実装するメソッドとなっています。でないと、メモリリークを起こしてしまい、システムの動作が重くなるなどの問題が発生してしまいます。
研修5日目
最終日は、各チームが作成したアプリケーションの発表会を行いました。どのチームもたった1日で、とても素敵なアプリを作成していて、聞いていてとても楽しかったです。
また、他のチームでも、自分たちのチームと同じライブラリを使用してボイスチェンジャーを作成していたチームがあり、そのチームは音声のピッチの変更に成功していました。原因は、そのライブラリが、iOSに対応していなかったことが原因でした。
Flutterはマルチプラットフォーム対応ですが、やりたい事によってはライブラリがiOSに対応していないことがあるということをここで学びました。
まとめ
1人を除いた新卒メンバーがネイティブ開発未経験だったのにも関わらず、最終的にネイティブアプリの開発まで行けたのは、今回のネイティブ研修のゴールとしてはすごく良かったんじゃないかと思いました。私はこれからバックエンドエンジニアとして業務に携わることになりますが、将来、何かの縁でネイティブアプリ開発に携わることがあれば、今回の研修内容を思い出して実装をしていこうと思います。
▼採用情報
レアゾン・ホールディングスは、「世界一の企業へ」というビジョンを掲げ、「新しい"当たり前"を作り続ける」というミッションを推進しています。
現在、エンジニア採用を積極的に行っておりますので、ご興味をお持ちいただけましたら、ぜひ下記リンクからご応募ください。
