スマホ用電子証明書を搭載していても要注意!「マイナ免許証」でスマホ単体での運転ができない理由

2025年3月から運用が開始された「マイナ免許証(マイナンバーカードと一体化した運転免許証)」ですが、ニュースなどで話題になるにつれて、このような誤解が増えています

「スマホにマイナンバーカードの機能を入れれば、財布やカードを持たずにスマホだけで運転できるのでは?」

結論からお伝えすると、現時点において、スマホ単体で運転免許証の代わりとすることはできません。 スマートフォンだけで運転すると「免許不携帯」の取締り対象になってしまいます。

ここでは、なぜスマホ単体での運転ができないのか、その理由と現在の正しいルールを分かりやすく解説します。

 

🤔 スマホにマイナンバーカードを搭載しても「免許不携帯」になる理由

マイナンバーカードの機能(スマホ用電子証明書)をAndroidスマートフォンなどに搭載する動きが進んでいますが、現行の道路交通法および運用ルールには以下のような壁があります。

警察官による携帯義務の規定
道路交通法では、自動車を運転する際は「運転免許証を携帯していなければならない」と定められています。

スマホ画面の提示だけでは法的な証明として認められない
警察の取り締まりや検問を受けた際、現在のところ警察側のシステムや現場の運用において、スマホ画面や電子証明書のみで運転免許情報をリアルタイムかつ法的に有効に確認するインフラが全国の現場で完全に網羅されているわけではありません。

実物の「携帯」が必須
したがって、スマホにマイナンバーカードの機能が入っていたとしても、現時点では「実物のマイナンバーカード」または「従来の緑・青・金色のプラスチック製運転免許証」のどちらかを物理的に財布やカードケースに入れて携帯している必要があります。

 

🤔 マイナ免許証の本当の仕組みとメリット

「それならマイナ免許証にする意味はあるの?」と思われるかもしれませんが、マイナ免許証には以下のような確実なメリットがあります。

住所変更などの手続がワンストップ化
警察署や運転免許センターに行かなくても、マイナンバーカードの住所変更手続を行えば、自動的に免許証の記載事項変更も連動して行えるようになります(※マイナポータル等を活用したスムーズな連携)。

更新時の講習のオンライン化(優良運転者など)
条件を満たした優良運転者などは、更新時の講習を自宅のパソコンやスマホからオンラインで受講できるようになり、免許センターでの拘束時間が短縮されます。

カード1枚への集約
財布の中に入れるカード類をマイナンバーカード1枚にまとめられるスッキリ感があります。

 

🤔 将来的にスマホだけで運転できるようになる?

政府やデジタル庁、警察庁では、デジタル社会の推進に向けて各種身分証のスマホ完全対応(デジタルID化)に向けた検討や実証実験を段階的に進めています。

将来的には、スマートフォン自体が完全に運転免許証の法的効力を持つ日が来る可能性は十分にありますが、現時点(2026年時点)においては、まだ過渡期です。

 

🤔 まとめ:ドライブの際は必ず「実物」の携帯を!

「マイナ免許証にしたから、スマホさえあれば手ぶらでドライブに出かけられる!」と油断してスマホだけで運転してしまうと、免許不携帯(反則金等の対象)として交通違反を切られてしまうおそれがあります。

マイナ免許証をお持ちの方も、従来の運転免許証をお使いの方も、車を運転する際は必ず実物のカード(マイナンバーカードまたは従来の免許証)を携帯するよう、十分にご注意ください。


ViewModel における stateIn の実力

Kotlin Flow を使って、UI State を宣言的に構築する

Jetpack Compose の ViewModel では、UI State を手動で管理し、init から load() を呼び出し、データが変わるたびに複数のプロパティを更新するコードをよく見かけます。

この方法でも問題なく動作します。しかし、アプリが複雑になるにつれて、複数の状態を同期させるためのコードが増え、管理が難しくなっていきます。

Kotlin Flow を使えば、もっと宣言的に UI State を構築できます。

Flow のオペレーターと stateIn を組み合わせることで、UI State がどの入力から、どのように導出されるのかをコードで表現できます。更新のたびに状態を手動で同期する必要はありません。

 

🤔 手動で UI State を管理するコスト

まずは、よくある ViewModel を見てみましょう。


class MyViewModel(
    private val repository: MyRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(UiState())
    val uiState = _uiState.asStateFlow()

    init {
        load()
    }

    private fun load() {
        viewModelScope.launch {
            val items = repository.loadItems()

            _uiState.update {
                it.copy(items = items)
            }
        }
    }
}

これは正当な実装です。一度だけデータを取得するような処理なら、理解しやすく、十分に実用的です。

ただし、ViewModel は次のような責務を担っています。

・ 初回ロードを開始する。
・ データ取得後に UI State を更新する。
・ 変更していないフィールドを保持する。
・ 入力が増えたときも、複数の状態の整合性を維持する。

画面が複雑になるほど、こうした処理が繰り返し必要になります。

では、Repository がすでに Flow でデータを公開している場合はどうでしょうか。Repository の Flow を手動で収集し、その値を別の StateFlow にコピーし続ける必要があるのでしょうか。

多くの場合、その必要はありません。

 

🤔 そもそも load() を呼ぶ必要はあるのか?

Jetpack Compose では、次のようなコードをよく使います。


LaunchedEffect(Unit) {
    viewModel.load()
}

あるいは、ViewModel の init ブロックから呼び出します。


init {
    load()
}

どちらも適切な場面があります。ユーザーが明示的に実行する更新処理や、一度だけ行う操作、特定のイベントに応じた処理には、明示的なコマンドが必要です。

しかし、画面が Flow で公開されたデータをそのまま表示するだけなら、手動でロードを開始し、UI State に反映させるための処理は不要かもしれません。

ここで重要なのは、コマンドと状態を区別することです。

・ コマンドは「何を実行するか」を表す。
・ 状態は「現在どうなっているか」を表す。

既存の Flow から UI State を導出できるなら、stateIn を使うことで、命令的な更新処理を減らせます。

 

🤔 stateIn で Flow を UI State に変換する

Repository が次のような Flow を公開しているとします。


interface MyRepository {
    val items: Flow<List<Item>>
}

この場合、ViewModel は UI State を直接導出できます。


class MyViewModel(
    repository: MyRepository
) : ViewModel() {

    val uiState: StateFlow<UiState> =
        repository.items
            .map { items ->
                UiState(items = items)
            }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = UiState()
            )
}

何がなくなったでしょうか。

・ 同じ UI State を手動更新するための MutableStateFlow
・ 初回収集を開始するためだけの init ブロック
・ Repository の値をコピーするためだけの load() 呼び出し
・ Repository のデータと UI State を同期させる処理

コードは、データと UI State の関係を直接表現しています。


Repository Flow
       |
       v
      map
       |
       v
     UiState
       |
       v
    stateIn
       |
       v
StateFlow<UiState>
       |
       v
   Compose UI

Repository が新しいリストを発行すると、それに対応する UiState が生成されます。

stateIn は、上流の Flow をホットな StateFlow に変換し、最新の値を共有するとともに、初期値を提供します。

公式ドキュメントでも、この用途が説明されています。

👉 stateIn – Kotlin Programming Language
👉 StateFlow – Kotlin Programming Language

変更のたびに状態を同期する方法ではなく、状態がどのように導出されるのかをコードで宣言できるのです。

 

🤔 複数の入力から、一貫した UI State を導出する

このメリットは、画面が複数の入力に依存するときに、さらに明確になります。

例えば、検索機能のあるリスト画面を考えてみましょう。表示するアイテムは、Repository のデータと検索クエリの両方に依存します。

手動で状態を更新する場合、両方の変更を考慮して UI State を更新しなければなりません。

Flow なら、その依存関係を明示できます。


class MyViewModel(
    repository: MyRepository
) : ViewModel() {

    private val query = MutableStateFlow("")

    val uiState: StateFlow<UiState> =
        combine(
            repository.items,
            query
        ) { items, query ->
            UiState(
                items = items.filter {
                    it.matches(query)
                },
                query = query
            )
        }.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = UiState()
        )

    fun onQueryChanged(query: String) {
        this.query.value = query
    }
}

ここで注目したいのは、UI アクションが更新するのは query だけだという点です。公開されている uiState を直接変更することはありません。

次のどちらが起きても、UI State は自動的に再計算されます。

・ Repository が新しいアイテムを発行する。
・ ユーザーが検索クエリを変更する。

表示リストを再計算する処理を、変更箇所ごとに書く必要はありません。

依存関係は combine の中で一度だけ定義されています。


Repository.items ──┐
                   ├── combine ──> UiState
query ─────────────┘
                                  |
                                  v
                               stateIn
                                  |
                                  v
                          StateFlow<UiState>

この設計では、入力を変更する処理と、その入力から UI State を導出する処理が明確に分離されています。

 

🤔 コマンドは明示的に、状態は宣言的に

もちろん、すべての操作を Flow で表現すべきという意味ではありません。

フォームの送信、アイテムの削除、ユーザー操作に応じた明示的な再取得などは、ViewModel がコマンドとして処理する必要があるでしょう。

重要なのは、導出できる状態を同期するためだけに、命令的な処理を増やさないことです。

複数の観測可能な入力から UI State が決まるなら、map や combine で依存関係を表現し、UI に StateFlow を公開する必要がある場合は stateIn を使います。

設計上の役割を整理すると、次のようになります。

依存関係がコードに表れるため、状態がどこで、なぜ変わるのかを理解しやすくなります。

 

🤔 適切な Sharing Strategy を選ぶ

stateIn では、上流の Flow をいつ開始・停止するかを指定します。

よく使われる設定の一つが、次のものです。


SharingStarted.WhileSubscribed(5_000)

これは、最初の購読者が現れたときに上流の収集を開始し、最後の購読者がいなくなってから 5 秒後に収集を停止する設定です。

👉 stateIn – Kotlin Programming Language

5 秒の猶予があることで、画面遷移などによる短時間の購読解除で、上流の収集がすぐに停止・再開するのを避けられます。

ただし、次の2つは同じではありません。

・ Flow の収集が再開されること。
・ ネットワークリクエストが再実行されること。

画面が再表示されたときに API が再度呼び出されるかどうかは、上流の Flow がどのように実装されているかによります。

また、stateIn 自体がデータを取得するわけではありません。上流の Flow が発行した値を共有し、最新の状態として保持する仕組みです。

よく見かける設定をそのまま採用するのではなく、アプリのライフサイクルやデータ取得の要件に合わせて選びましょう。

 

🤔 MutableStateFlow が必要な場面もある

MutableStateFlow をなくすこと自体が目的ではありません。命令的に変更する必要がある入力には、引き続き有用です。

例えば、ユーザーが操作する検索クエリです。


private val query = MutableStateFlow("")

ユーザーの入力に応じて query を変更するのは自然な設計です。

一方、Repository のデータと検索クエリから導出できる uiState は、別の可変状態として保持する必要がありません。

実践的なルールは次のとおりです。

・ 入力には MutableStateFlow を使う。
・ Flow オペレーターで入力から状態を導出する。
・ stateIn で結果を StateFlow として公開する。

これは可変状態を排除する設計ではありません。可変性が必要な場所にだけ、それを配置する設計です。

 

🤔 まとめ:stateIn に状態管理を任せよう

stateIn は、Flow を StateFlow に変換するだけの便利なオペレーターではありません。map や combine と組み合わせることで、ViewModel の状態管理を宣言的に設計するための重要な手段になります。

データを手動で収集し、値をコピーし、関連するフィールドを同期する代わりに、入力と UI State の依存関係を直接表現できます。

特に、複数のデータソースから画面の状態が決まる場合に、そのメリットが大きくなります。

コマンドは明示的に。状態は宣言的に。

Flow で依存関係を定義し、適切な場面で stateIn を使って UI State を公開しましょう。


【モバイルSuica】新機能「teppay(テッペイ)」は不要?電車に乗るだけならスルーでOKな理由

2026年10月からモバイルSuicaで新たにスタートしたコード決済機能「teppay(テッペイ)」。

「Suicaの中に新しい決済機能が増えたみたいだけど、使ったほうがいいの?」
「設定とか面倒くさそうだけど、使わないと損する?」

そんな疑問を持っている方も多いのではないでしょうか。

結論から言うと、「電車やバスに乗るのがメイン」「普段の買い物はクレカや既存のPayで十分」という方は、teppayを使う必要は一切ありません。

この記事では、なぜ「普通の人」はteppayを使わなくて大丈夫なのか、その理由を分かりやすく解説します。

 

🤔 理由1:改札を通るときは結局「Suica(タッチ)」だから

teppayはPayPayや楽天ペイのような「バーコード・QRコード決済」です。

お店での買い物や送金には使えますが、電車の改札機をピッとタッチして通る機能は、これまで通りの「Suica」の役割です。

teppayを設定したからといって改札が便利になるわけではないため、電車移動がメインの人にはメリットがほぼありません。

 

🤔 理由2:普段のチャージなら「2万円上限」で十分

teppayの大きな特徴のひとつに「チャージ上限が最大30万円まで増える」という点があります。

ですが、毎日の電車代やちょっとしたコンビニの買い物程度なら、これまでのSuicaの上限である「2万円」があれば十分に足ります。

「30万円もスマホの残高に入れて持ち歩きたくない…」という人にとっても、無理に導入する理由は見当たりません。

 

🤔 理由3:画面の切り替えや操作が増えて面倒になる

これまでモバイルSuicaは、アプリを開かなくてもスマホをかざすだけで使えました。

しかしteppayで支払いをしようとすると、アプリを開いて「Suica画面」から「teppay画面」へ切り替え、バーコードを提示するといった手順が必要になります。

「かざすだけ」のシンプルさに慣れている人からすると、かえって手間が増えてしまいます。

 

🤔 理由4:ポイ活目的なら他のPayやクレカでOK

teppayを使うと「teppayポイント」という新しいポイントが貯まりますが、すでにPayPay、楽天ペイ、d払いなどの既存コード決済を使っている人や、クレジットカードのポイントを貯めている人にとっては、管理するポイントが増えて煩雑になるだけです。

無理に新しいポイントプログラムに乗り換える必要はありません。

 

🤔 まとめ:これまで通り「ピッとタッチ」で快適に使おう!

teppayは「高額な定期券を残高で買いたい人」や「Suicaアプリ内で友達と送金し合いたい人」などに向けた新機能です。

・ 電車やバスに乗るのがメイン

・ チャージは2万円以下で問題ない

・ 支払いはかざすだけの電子マネーが良い

これらに当てはまる方は、teppayの機能を気にせず、これまで通りのモバイルSuicaの使い方を続けて全く問題ありません。

便利な機能が増えるのは良いことですが、自分に不要な機能は無理に使わず、シンプルで快適なキャッシュレス生活を送りましょう!


uiState を宣言的に組み立てる Flow オペレーター ベスト10

みんなはいくつ使っていますか。

 

🧑🏻‍💻 1. stateIn: Flow を StateFlow に変換する

宣言的な uiState の締めくくりです。WhileSubscribed(5_000) にしておくと、画面回転では再購読せず、バックグラウンドでは上流を止められます。


val uiState: StateFlow<UiState> = flow
    .stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = UiState.Loading
    )

 

🧑🏻‍💻 2. combine: 複数ソースを 1 つの UiState に合成する

MutableStateFlow を何本も手で更新する代わりに、「ソースの合成結果 = UiState」と宣言できます。


combine(repo.items, query, filter) { items, q, f ->
    UiState.Success(items.filter { it.matches(q, f) })
}

 

🧑🏻‍💻 3. map: ドメインモデルから UI モデルへの変換

整形や表示用の変換はここに集約します。


repo.user.map { it.toUiModel() }

 

🧑🏻‍💻 4. flatMapLatest: 入力の変化で上流を切り替える

検索クエリや選択 ID が変わると、前のリクエストを自動キャンセルして新しい Flow に切り替えます。


query.flatMapLatest { q -> repo.search(q) }

 

🧑🏻‍💻 5. catch: エラーを UiState に変換する

例外を UI の状態として扱えます。上流のエラーだけを捕捉する点が try/catch との違いです。


.catch { emit(UiState.Error(it.message)) }

 

🧑🏻‍💻 6. onStart: Loading の初期値を流す

購読開始時に Loading を流す定番の書き方です。


repo.items
    .map<_, UiState> { UiState.Success(it) }
    .onStart { emit(UiState.Loading) }

 

🧑🏻‍💻 7. distinctUntilChanged: 無駄な再描画を防ぐ

StateFlow 自体も同値を弾きますが、中間の Flow で重い処理の前に挟むと効果的です。


query.distinctUntilChanged()
    .flatMapLatest { repo.search(it) }

 

🧑🏻‍💻 8. debounce: 入力の連打を間引く

検索ボックスなどで使います。debounce は同値の弾き込みはしないため、distinctUntilChanged と併用します。


query.debounce(300)
    .distinctUntilChanged()

 

🧑🏻‍💻 9. scan / runningFold: 状態を畳み込む

ページング風の追加読み込みや、イベントから状態を積み上げる MVI 的な設計で使います。


events.scan(UiState()) { state, event -> reduce(state, event) }

 

🧑🏻‍💻 10. flowOn: 上流の実行スレッドを指定する

重い変換や IO を Main から外します。上流にだけ効く点に注意してください。


repo.items
    .map { heavyTransform(it) }
    .flowOn(Dispatchers.Default)

 

🧑🏻‍💻 よくある組み合わせ例


class SearchViewModel(repo: SearchRepository) : ViewModel() {
    private val query = MutableStateFlow("")
    private val filter = MutableStateFlow(Filter.All)

    val uiState: StateFlow<UiState> = combine(
        query.debounce(300).distinctUntilChanged()
            .flatMapLatest { repo.search(it) },
        filter
    ) { results, f ->
        UiState.Success(results.filter(f::accepts)) as UiState
    }
        .onStart { 
            emit(UiState.Loading) 
        }
        .catch { 
            emit(UiState.Error(it.message)) 
        }
        .flowOn(Dispatchers.Default)
        .stateIn(
            viewModelScope, 
            SharingStarted.WhileSubscribed(5_000), 
            UiState.Loading
        )

    fun onQueryChange(q: String) { 
        query.value = q 
    }
}

 

🧑🏻‍💻 参考


UI Action は UiState を直接変更しない

UiState は入力から導出する

UI = f(State) は、宣言的 UI の基本的な考え方です。

では、UIからのActionによってStateをどう変えるのでしょうか?

ルールはシンプルです。

UI Action から public な UiState を直接変更しない。

UI Action は ViewModel が持つ private な入力を更新します。

そして、UiState はその入力から導出します。


UI Action
  ↓
Private Flow
  ↓
UiState
  ↓
UI

例えば、検索画面なら次のように書けます。


private val _query = MutableStateFlow("")

val uiState = combine(
    _query,
    repository.items
) { query, items ->
    UiState(
        query = query,
        items = items
    )
}

fun onQueryChanged(query: String) {
    _query.value = query
}

次のように UiState を直接変更することもできます。


fun onQueryChanged(query: String) {
    _uiState.update {
        it.copy(query = query)
    }
}

この場合、UI Action 自身が画面の State を管理することになります。

一方、private な入力を更新するだけなら、責務が明確になります。


UI Action
    ↓
入力を変更する
    ↓
UiState を導出する
    ↓
UI を描画する

つまり、


UiState = f(Inputs)

であり、


UI Action → UiState を変更する

ではありません。

UI Action の役割は、「何が起きたか」を ViewModel に伝えることです。

そして、「その結果どのようなUiStateになるか」は Flow の依存関係によって決まります。

UI Action は UiState を変更する命令ではなく、入力を変更するトリガーである。

 

🧑🏻‍💻 参考