HAMADAの語り草

興味のある技術のアウトプットをしたいと思います

CA Tech Dojo ~Android 編~ に参加した話

はじめに

会津大学学部三年のHAMADAです。すっかり春の陽気で、新学年に心躍らせている今日この頃です。さて今回は、CA Tech Dojo ~Android 編~ に参加した振り返りをしていこうと思います。

参加した経緯

2023年の6月ごろから、大学の先輩の勧めでAndroidを書き始めました。学内のハッカソンを始めとして、少しずつAndroid開発に取り組んでいました。しかし、個人の学習では、学び方に偏りがあるということをひしひしと感じていました。そこで、CA Tech Dojo ~Android 編~ (以下Dojoとします)に参加することで、体系的なAndroid開発の知識を身につけ成長したいと考えました。

Androidで参加するインターンは初めてだったので、選考通過の連絡が来たときは非常に嬉しかったです!

目標

インターンに参加する際に以下のような目標を立てました

  • 言語化をできるようにする
  • 作りたいプロダクトを実装できる技術力をつける

言語化をできるようにする

なんとなく動くコードは作れるけど、なぜその実装にしたのか、なぜ動くのかを理解できずに雰囲気で実装している。ということが今まで多くありました。コードの意味や挙動を、言語化できるようにすることで、より良い実装や理解に近づくと考えました。また、質問や自分の考えを相手に的確に迅速に伝えるということを意識することで、開発だけでなく、日常生活を含めた色々な場面で円滑なコミュニケーションをとることができると考えています。これらを向上させるために、言語化をできるようにする。という目標を立てました。

作りたいプロダクトを実装できる技術力をつける

Dojoに参加する前の実装では、自分ができる範囲の実装に逃げるようなことが多く、プロダクト自体の質を下げていました。もちろん、期間が限られていない開発では、チャレンジングな実装に取り組んできたつもりですが、期間が限られた開発ではできる実装を繰り返していたように思います。そこで、自分が想像する妥協のないプロダクト作りができるようになろうとこの目標を立てました。具体的には、苦手なUI実装や状態の管理、設計を意識した実装ができるようになるという目標です。

インターンに参加

このインターンの期間は3/6~3/12までの一週間でした。一日一日振り返ってみたいと思います。

3/6

この日から、開発が始まるということでお題がオープニングで発表されました。

お題は”AIチャットアプリ”。ChatGPTのAPIを用いたアプリケーションでした。

参加者は私を含めて10人で、3,3,4人のチームに分かれて開発を行います。

チームではありますが、チームメンバーとは教えあったり、質問しあったりしながら各々のアプリケーションを実装していきます。もちろん、チームを跨いだ質問や教え合いもOKで私も他のメンバーにたくさん教えてもらいました!

~3/6のお昼ご飯~

午後は、本格的に開発に取り組んでいきます。

1日目は以下のようなことに取り組みました。

  • ChatGPTに質問を送り返信を取得する
  • 会話表示画面の実装

RetrofitとSerializationを用いて、ChatGPTからの返答を取得することができました。

どちらも今回初めて触った技術だったのですが、なんとか実装することができました。

~余談~

私は、龍角散のど飴が非常に好きなのですが、龍角散トークで盛り上がれるエンジニアの方がいらっしゃって非常に嬉しかったです。

3/7

2日目からは、午前中から開発に取り組んでいきます。

2日目は以下のようなことに取り組みました。

  • 質問と返答を画面に表示する。
  • 会話一覧画面のDrawerの実装

画面の表示では、sealed Interfaceを用いて、状態に応じた実装をすることができました。

Drawerの実装では、”Drawer”という名前を知らず、調べるのに苦労しましたが、メンターさんや他のメンバーから教えたいただき事なきを得ました。

他のメンバーとも二日目には、すっかり打ち解け開発以外もとても楽しい時間を過ごしました!

~3/7のお昼ご飯~

3/8

3日目は以下のようなことに取り組みました。

  • Textから受け取ったAPI KEYを永続化する
  • 会話をツリー状にする

他にも、ライブラリの選定方法と基準についてや、kotlinで拡張関数を使うタイミングやAndroid開発における慣習など、参加前から疑問点も解消することができました。

~3/8のお昼ご飯~

3/9, 3/10

土日は他のメンバーと集まって開発をしたり、秋葉原で遊んだりして親睦を深めました。

一緒に、Android開発に取り組む仲間を作れることもこのインターンの醍醐味だと思います。

土日にも開発を進めました

  • SharedPreferencesの実装
  • Roomの実装
  • material2からmaterial3への移行

3/11

4日目は以下のようなことに取り組みました。

  • RoomからFirestoreへの移行
  • UI全体の実装

3日目には必須要件の実装が終わっていたので、土曜日からは、ブラッシュアップや今まで触ったことのない技術に取り組みました。

Factory周りの実装で、依存性注入で苦しみました。Factoryの実装後、メンターさんのアドバイスでhiltを導入しhiltの便利さを実感しました。ただ、便利なものを使うのではなく、不便さを知ってから、意義や便利さを理解したのちに便利なライブラリを利用する。という経験ができたことも大きな学びになりました。

~3/11のお昼ご飯~

3/12

この日はついに最終発表日!

実装としては、以下のようなことに挑戦しました。

  • UIにアニメーションをつけてリッチにする。
  • ダイナミックリンクを発行し、リンクを共有できるようにする。

リンクの共有は間に合わず中途半端な実装になってしまいましたが、仕組みを学べたので今後にうまく活用していきたいです。

~3/12のお昼ご飯~

その後、15:00にコードフリーズとなり、15:00~16:00の1時間で発表の資料を作ります。1時間で発表資料を作るのが、かなり大変でした…

発表中

デモとアプリマップ

youtu.be

チームのメンターさんと1on1面談

最後に、メンターさんと1on1面談を行いました。メンターさんからは、goodポイントとmoreポイントを教えていただきました!

goodポイントは、必要な機能の実装力があるということ。Androidにとらわれない、今までのプログラミング経験を用いて、実装し切ることができるとおっしゃっていただきました。また、sealed interfaceをうまく活用できている点や、期間中、状態やview modelの受け渡しについてアドバイスをいただいた所を、最終日には改善できていたところも良いとおっしゃっていただきました!

目標にしていた言語化については、質問をたくさん投げられたこと、現状の課題を自身で理解し説明できているということをメンターさんと確認しました!

moreポイントは、Android Studioに親しみを持つこと(ショートカット等を使える)、stateを実装する際にbyを活用すること。そして、設計を意識した実装で何かアプリを作ってみるともっと成長できるとおっしゃっていただきました!

懇親会

懇親会では、ドリンクとケータリングをいただきながら

など

趣味の話からキャリアの話まで、幅広く社員の方々やDojoメンバーと話すことができました。

Dojoに参加することができて

Dojoに参加していた一週間は、今までで一番開発に真摯に向き合えた期間だと感じています。技術的成長を感じることができたのは勿論、Android開発を共にする仲間を作ることができたことは、これからエンジニアとしての道を歩む上での大きな財産となりました。勿論、Dojoを完走したということがゴールではありません。Dojoでの経験や高まったモチベーションをこれからの開発に大いに活かしたいと思います。

何やら色々書いてきましたが、一つ伝えたいことがあるとすれば、”めちゃくちゃ楽しいインターン”だったということです。Android開発は勿論、Dojoメンバーとご飯に行ったり、発表後温泉に行ったりと充実していました。全国各地から様々なバックグラウンドを持つみんなと、Android開発という共通項を通じて、仲を深めることができ非常に楽しかったです。来年、Dojoへの参加を検討している方がいらっしゃれば、強くお勧めしたいと思います。

インターン期間中、関わってくださった皆様、本当にありがとうございました!

次は、Droid Kaigiで集まれたらいいな…

所属サークル

https://twitter.com/aizu_PxL

私のTwitter

https://twitter.com/AHMOS_HMD

私のgithub

ahmos0 (HAMADA) · GitHub

鬼の2時間ハッカソン?でAndroidアプリの録音再生機能を実装した話

はじめに

会津大学学部3年のHAMADAです。この記事は「Aizu Advent Calendar 2023」18日目の記事です。今回は、鬼の2時間ハッカソンで試したことについて書いていきたいと思います。

経緯

つい最近、私が所属しているA-PxLで ”2時間ハッカソン” と題して、普段の定例会の2時間を使って、テーマが”クリスマス”のプロダクトを一本作り切るという鬼企画が開催されました。

A-PxLはXRのサークルなので、私もAndroidのARアプリを作成しようとしたのですが、ARCore周りの設定で1時間15分も溶かしてしまいました。流石にちょっと厳しそうだったので、方向転換をし、音声録音アプリケーションを作成することにしました。

一応テーマがクリスマスなので、プレゼントを届けに来たサンタさんに”メリークリスマス”と一言伝えられたら素敵やな、とか後付け的に思いついたので、テーマには沿ってると思います…

使用したもの・技術

実装

まず、録音をするためにAndroidManifest.xmlに以下を追記する。

<uses-permission android:name="android.permission.RECORD_AUDIO" />

MediaRecorderとMediaPlayerを使用して、

以下のように実装しました。

import android.media.MediaRecorder
import android.media.MediaPlayer

class AudioRecorder(private val filePath: String) {

    private var mediaRecorder: MediaRecorder? = null
    private var mediaPlayer: MediaPlayer? = null
    private var isRecording = false

    fun startRecording() {
        if (isRecording) return

        mediaRecorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            setOutputFile(filePath)
            prepare()
            start()
        }
        isRecording = true
    }

    fun stopRecording() {
        if (!isRecording) return

        mediaRecorder?.apply {
            stop()
            release()
        }
        mediaRecorder = null
        isRecording = false
    }

    fun playRecording() {
        mediaPlayer = MediaPlayer().apply {
            setDataSource(filePath)
            prepare()
            start()
        }
    }
}

MediaRecorder

setAudioSource(MediaRecorder.AudioSource.MIC) は音をマイクから取ってくるように設定しています。他にも電話から音をとったり、リンクから音を取ったりと色々設定できます。

setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) はアウトプットのフォーマットを設定しています。

setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)エンコードの形式を設定しています。

setOutputFile(filePath) は設定したファイルパスにファイルをアウトプットします。

prepare() で初期化を完了します。

start() でレコーダーを開始。

stop() でレコーダーを停止。

release() でリソースを解放。

MediaPlayer

setDataSource(filePath) は再生するソースをセットします。

prepare() で初期化を完了します。

start() で再生を開始。

上記のような処理を呼び出す際に、使っているハードでパーミッションがあるかどうかを確認するために、以下のような実装をしました。

    private fun handleRecording() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 200)
        } else {
            audioRecorder?.startRecording()
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == 200 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            audioRecorder?.startRecording()
        }
    }

アプリがRECORD_AUDIOのパーミッションを持っているかどうか確認して、持っていれば、そのままレコーディングを開始し、持っていなければ以下のようなリクエストを表示します。

リクエストに対してのユーザの応答を確認し、許可が得られていれば録音を開始します。

Andoidの実機が手元になく、デモが撮れませんでした。ごめんなさい...

おまけ

UIとしては、本当にシンプルで雑なUIなのですが、ChatGPT君にいい感じの画像を作ってもらい背景にしたところそれっぽい見た目になりました。

Before

After

感想と展望

45分で雑な実装にはなってしまいましたが、パーミッション周りは触ったことがなかったので、良い学びになりました。二時間でのハッカソンはかなり鬼畜の所業と言わざるを得ませんが、実装したい機能、やったことない機能を勉強するにはちょうど良いので、ぜひ次もやってみたいと思います。ここまで読んでいただきありがとうございます。良いクリスマス、良い年末をお迎えください。

所属サークル

https://twitter.com/aizu_PxL

私のTwitter

https://twitter.com/AHMOS_HMD

私のgithub

ahmos0 (HAMADA) · GitHub

参考リンク

developer.android.com

developer.android.com

学祭ハッカソンの技術まとめ (Apollo Kotlin編)

はじめに

会津大学学部三年のHAMADAです。この記事は「学生iOS&Androidエンジニア Advent Calendar 2023」8日目の記事です。何回かに分けて、学祭ハッカソンで学んだ技術を振り返りたいと思います。今回は、Apollo Kotlinについて綴っていきたいと思います。

アプリの概略

アプリの概略や、ハッカソン自体の記録は以下の記事をご一読いただけると幸いです。

hahahamada.hatenablog.com

ハッカソンでの主な担当部分

  • GraphQLとDynamoDBを用いたバックエンド部分の実装
  • Jetpack ComposeにおけるUI実装
  • クラウドサービスまわり

等々

まとめたいこと

  • Apollo Kotlin を用いた、GraphQL Serverとの繋ぎこみと主な実装
  • GoogleMapAPIを用いた実装
  • Jetpack Composeを用いたUI実装

    //かなり汚くて荒い実装をしたので、設計の勉強のネタとして使いたいですね。

今回説明すること

  • Apollo Kotlin を用いた、GraphQL Serverとの繋ぎこみと主な実装

Apollo Kotlinを用いた、GraphQL Serverとの繋ぎこみと主な実装

Apollo Kotlinは、GraphQLクエリからKotlin、Javaのモデルを生成するGraphQLクライアントです。今回は、Apollo Kotlinを用いて、Goで立てたGraphQLサーバーに対し、ミューテーションやクエリを実行するというものになっています。(DynamoDBを今回使用しているので、GoでGraphQLサーバーを立てずとも、AppSyncを使用した方が筋が良かった気もしています。)

Goで立てた、GraphQLサーバーの実装については以下のリポジトリをご覧ください

https://github.com/ahmos0/ATUMARE_GraphQLServer

github.com

まず、Apollo Kotlinを使用するためにbuild.gradle(:app)に以下を記述します。

plugins{
    //以下を追加
    id 'com.apollographql.apollo3'
}

apollo {
  service("service") {
    packageName.set("com.example.exampleserver")
  }
}

dependecies {
    //以下を追加
    implementation 'com.apollographql.apollo3:apollo-runtime:3.8.2'
}

次に、schema.graphqlsを取得します。

プラグインが自動的に作成するapolloDownloadSchema Gradleを使用して、スキーマを取得します。

以下を実行します。

endpointは任意のものに変更してください。

./gradlew :app:downloadApolloSchema --endpoint='https://hogehoge.com/graphql' --schema=app/src/main/graphql/schema.graphqls

すると以下のような、schema.graphqlsが取得できます。

長いので、折りたたんでおきます。

schema.graphqls

"""
The `Boolean` scalar type represents `true` or `false`.
"""
scalar Boolean

"""
One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.
"""
type __EnumValue {
  deprecationReason: String

  description: String

  isDeprecated: Boolean!

  name: String!
}

"""
Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.
"""
type __InputValue {
  """
  A GraphQL-formatted string representing the default value for this input value.
  """
  defaultValue: String

  description: String

  name: String!

  type: __Type!
}

"""
Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.
"""
type __Field {
  args: [__InputValue!]!

  deprecationReason: String

  description: String

  isDeprecated: Boolean!

  name: String!

  type: __Type!
}

type Mutation {
  putItem(time: String!, capacity: Int!, uuid: String!, name: String!, departure: String!, destination: String!, passenger: Int!,passengers: [PassengerInput]): Item
  incrementPassenger(uuid: String!, name: String!,  passengers: [PassengerInput]): Item
}

input PassengerInput{
  namelist: String!,
  comment: String!
}

"""
The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.

Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.
"""
type __Type {
  description: String

  enumValues("" includeDeprecated: Boolean = false): [__EnumValue!]

  fields("" includeDeprecated: Boolean = false): [__Field!]

  inputFields: [__InputValue!]

  interfaces: [__Type!]

  kind: __TypeKind!

  name: String

  ofType: __Type

  possibleTypes: [__Type!]
}

type Query {
  allItems: [Item]
}

type Item {
  capacity: Int

  departure: String

  destination: String

  name: String

  time: String

  uuid: String

  passenger: Int

  passengers: [PassengerModel]
}

type PassengerModel {
  namelist: String!,
  comment: String!
}

"""
An enum describing what kind of type a given `__Type` is
"""
enum __TypeKind {
  """
  Indicates this type is a list. `ofType` is a valid field.
  """
  LIST

  """
  Indicates this type is a non-null. `ofType` is a valid field.
  """
  NON_NULL

  """
  Indicates this type is a scalar.
  """
  SCALAR

  """
  Indicates this type is an object. `fields` and `interfaces` are valid fields.
  """P
  OBJECT

  """
  Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.
  """
  INTERFACE

  """
  Indicates this type is a union. `possibleTypes` is a valid field.
  """
  UNION

  """
  Indicates this type is an enum. `enumValues` is a valid field.
  """
  ENUM

  """
  Indicates this type is an input object. `inputFields` is a valid field.
  """
  INPUT_OBJECT
}

"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""
scalar String

"""
The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. 
"""
scalar Int

"""
A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.
"""
type __Schema {
  """
  A list of all directives supported by this server.
  """
  directives: [__Directive!]!

  """
  If this server supports mutation, the type that mutation operations will be rooted at.
  """
  mutationType: __Type

  """
  The type that query operations will be rooted at.
  """
  queryType: __Type!

  """
  If this server supports subscription, the type that subscription operations will be rooted at.
  """
  subscriptionType: __Type

  """
  A list of all types supported by this server.
  """
  types: [__Type!]!
}

"""
A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. 

In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.
"""
type __Directive {
  args: [__InputValue!]!

  description: String

  locations: [__DirectiveLocation!]!

  name: String!

  onField: Boolean! @deprecated(reason: "Use `locations`.")

  onFragment: Boolean! @deprecated(reason: "Use `locations`.")

  onOperation: Boolean! @deprecated(reason: "Use `locations`.")
}

"""
A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.
"""
enum __DirectiveLocation {
  """
  Location adjacent to an input object field definition.
  """
  INPUT_FIELD_DEFINITION

  """
  Location adjacent to a fragment definition.
  """
  FRAGMENT_DEFINITION

  """
  Location adjacent to a field definition.
  """
  FIELD_DEFINITION

  """
  Location adjacent to an input object type definition.
  """
  INPUT_OBJECT

  """
  Location adjacent to an interface definition.
  """
  INTERFACE

  """
  Location adjacent to a mutation operation.
  """
  MUTATION

  """
  Location adjacent to a schema definition.
  """
  SCHEMA

  """
  Location adjacent to a scalar definition.
  """
  SCALAR

  """
  Location adjacent to an enum value definition.
  """
  ENUM_VALUE

  """
  Location adjacent to a query operation.
  """
  QUERY

  """
  Location adjacent to a fragment spread.
  """
  FRAGMENT_SPREAD

  """
  Location adjacent to an argument definition.
  """
  ARGUMENT_DEFINITION

  """
  Location adjacent to a object definition.
  """
  OBJECT

  """
  Location adjacent to a union definition.
  """
  UNION

  """
  Location adjacent to an enum definition.
  """
  ENUM

  """
  Location adjacent to a subscription operation.
  """
  SUBSCRIPTION

  """
  Location adjacent to a field.
  """
  FIELD

  """
  Location adjacent to an inline fragment.
  """
  INLINE_FRAGMENT
}

"""
Directs the executor to include this field or fragment only when the `if` argument is true.
"""
directive @include ("Included when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT

"""
Directs the executor to skip this field or fragment when the `if` argument is true.
"""
directive @skip ("Skipped when true." if: Boolean!) on FIELD|FRAGMENT_SPREAD|INLINE_FRAGMENT

"""
Marks an element of a GraphQL schema as no longer supported.
"""
directive @deprecated ("Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formattedin [Markdown](https:\/\/daringfireball.net\/projects\/markdown\/)." reason: String = "No longer supported") on FIELD_DEFINITION|ENUM_VALUE

schema {
  query: Query
  mutation: Mutation
}

これを元に、このスキーマ定義を元に必要なミューテーション、クエリを書きます。

今回、必要な処理は

  • 募集登録画面で情報を登録するミューテーション(PutItem)

  • 募集一覧を取得するクエリ(AllItems)

  • 乗車登録画面から登録した際に、乗員の人数を増やすミューテーション(IncrementPassenger)

  • 運転手待機画面での情報取得(こちらは実装が間に合っていないのでmock画面のみです)

実装はそれぞれ以下

//itemquery.graphql
query AllItems {
    allItems {
        uuid
        name
        departure
        destination
        time
        capacity
        passenger
        passengers {
             namelist,
             comment
        }
    }
}
//mutations.graphql
mutation PutItem($uuid: String!, $name: String!, $departure: String!, $destination: String!, $time: String!, $capacity: Int!, $passenger: Int!, $passengers: [PassengerInput]) {
    putItem(uuid: $uuid, name: $name, departure: $departure, destination: $destination, time: $time, capacity: $capacity, passenger: $passenger, passengers: $passengers) {
        uuid
        name
        departure
        destination
        time
        capacity
        passenger
    }
}

mutation IncrementPassenger($uuid: String!, $name: String!, $passengers:[PassengerInput]) {
    incrementPassenger(uuid: $uuid, name: $name,  passengers: $passengers) {
        uuid
        name
        passengers {
            namelist
            comment
        }
    }
}

これらを定義した後に、プロジェクトをbuildするとプラグインはモデルを生成するために、generateApolloSourcesというタスクを実行します。

これによって、GraphQLクエリ、ミューテーションからkotlinのソースコードが自動生成されます。

その時の名称は、PutItemMutation.kt, IncrementPassengerMutation.kt, AllItemsQuery.ktのように”Mutation” “Query”が末尾についたものとなります。

これらのソースコードを用いた実装が以下になります。

まず、ApolloClientのインスタンスを作成し、送信先のエンドポイントを指定します。

private val apolloClient = ApolloClient.Builder()
        .serverUrl(BuildConfig.serverEndPoint)
        .build()

それぞれのquery, mutationごとの実装は以下になります。

putItem

suspend fun putItem(
        uuid: String,
        name: String,
        departure: String,
        destination: String,
        time: String,
        capacity: Int,
        passenger: Int,
        passengers: List<PassengerInput>? = null
    ): Result<PutItemMutation.PutItem?> {

        val convertedPassengers = passengers?.map { PassengerInput(it.namelist, it.comment) }

        val mutation = PutItemMutation(
            uuid = uuid,
            name = name,
            departure = departure,
            destination = destination,
            time = time,
            capacity = capacity,
            passenger = passenger,
            passengers = convertedPassengers?.let { Optional.Present(it) } ?: Optional.Absent
        )

        return try {
            val call: ApolloCall<PutItemMutation.Data> = apolloClient.mutation(mutation)
            val response: ApolloResponse<PutItemMutation.Data> = call.execute()
            val item = response.data?.putItem
            Result.success(item)

        } catch (e: ApolloException) {
            Result.failure(e)
        }
    }

fetchAllItems

suspend fun fetchAllItems(): Result<List<AllItemsQuery.AllItem>> {
        val query = AllItemsQuery()
        return withContext(Dispatchers.IO) {
            try {
                val response: ApolloResponse<AllItemsQuery.Data> = apolloClient.query(query).execute()
                Result.success(response.data?.allItems?.filterNotNull() ?: emptyList())
            } catch (e: ApolloException) {
                Result.failure(e)
            }
        }
    }

incrementPassenger

suspend fun incrementPassenger(uuid: String, name: String, namelist: String,comment: String) {
        val newPassengerInput = PassengerInput(namelist, comment)
        val passengerInputList = listOf(newPassengerInput)
        val mutation = IncrementPassengerMutation(uuid, name, Optional.Present(passengerInputList))

        try {
            val call: ApolloCall<IncrementPassengerMutation.Data> = apolloClient.mutation(mutation)
            call.execute()
            Result.success(Unit)
        } catch (e: ApolloException) {
            Result.failure(e)
        }
    }

これらを対応した画面から呼び出すことで、GraphQLサーバーを通したDBとのやりとりを実現しました。

感想と展望

そもそもGraphQLの実装部分で苦労しましたが、なんとか動くように実装できました。やはり、公式ドキュメントをしっかり読むことは大事だなと再確認できました。バックエンドとクライアント部分の両方に取り組んで実装できたことは、すごく大きな経験になりました。GoogleMapAPI周りでも詰まったところがあったので、近いうちにまとめたいと思います。コードがかなり稚拙ではありますが、何かの参考になれば幸いです。ここまで読んでいただきありがとうございます。

所属サークル

https://twitter.com/aizu_PxL

私のTwitter

https://twitter.com/AHMOS_HMD

私のgithub

ahmos0 (HAMADA) · GitHub

参考リンク

www.apollographql.com

学祭ハッカソンに参加した記録

はじめに

会津大学学部三年のHAMADAです。ブログを今年は頑張ると言ってずいぶん経ってしまいました。今回は、会津大学の蒼翔祭でハッカソンに参加したのでその振り返りをしたいと思います。記録としての側面が強いです。もし興味があれば、ご一読いただけると嬉しいです。

参加の経緯とメンバー集め

今まで色々ハッカソンには参加してきましたが、先輩方にお誘いいただいて参加することが多かったので、同級生や後輩を誘って開発をしてみたいということで、それにちなんだメンバーとハッカソンに参加しました。メンバーは、普段一緒にソフトテニスをやっている三年生の@M9oKp@wai_1002、フレッシュな一年生の@BurariHina524です。

ハッカソンの募集は夏休み前ごろで、当時学び始めていたJetpackComposeを使いたかったので、モバイルのアプリケーションが作りたいなと漠然と思っていました。三年生の二人に声をかけたところ快諾してくれました。@BurariHina524は、新入生向けの学内ハッカソンの際に、学祭ハッカソンの話をしていたことを覚えていてくれて、一緒に開発することとなりました。

開発内容の決定

今回のハッカソンのテーマは『会津をより良く、より便利に』ということで、初回のMTGでアイデア出しをした結果、高齢者や学生の移動の不便さを解決するようなプロダクトを作るという結論に辿り着き、ライドシェアアプリ『ATSUMARE』を作成することになりました。今回は、他のメンバーの技術スタックや興味、アプリケーションのプラットフォーム適正を考えて、モバイルアプリケーションを作ることに決定しました。

このアプリで解決したいこと

前述したように、会津若松市では高齢者や学生の移動の不便さが存在します。雪の季節には、車を持っていなければ、徒歩で移動するか公共交通機関を使うほかありません。そこで、目的地を共有する者同士でライドシェアすることでこの不便さを解消したいと考えました。また、地域の住民の方とお話をさせていただく中で、”会津大学の存在はもちろん知っているが、具体的に何をしているのかはよく知らない”というお話を耳にします。私自身も、住民の方のこと会津のことに関して、知らないことがたくさんあります。地域とのつながりを強くしお互いについて知ることで、より良い会津生活が送れるようになるのでは?と考えました。

使用技術の決定

今回はモバイルアプリケーションを作るということが決定したので、@wai_1002がSwiftUIを学んでいることと、私自身がJetpackComposeを学んでいることから、敢えてiOSAndroidを分けて実装することにしました。(FlutterやKMMも選択肢としてもちろんありましたが、学びたい技術を使用することもハッカソンの醍醐味だということで….)

また、バックエンド部分は私が触りやすいという安直な理由で、GoでGraphQLサーバーを実装しDynamoDBでデータの管理をすることにしました。

細かい技術の実装については、また別に記事にまとめたいと思います。

期間中にできたもの

アーキテクチャ

アプリロゴ

@BurariHina524が実装してくれました。

モバイルの画面

@wai_1002が実装してくれました。

バックエンド

GoでGraphQLサーバーを実装しました。乗客を集める際の要項を登録できるようにしたり、乗客がクリックした募集の内容を持って来ることができるようにしました。また、乗客が入力した名前や要望を、それに対応する募集のDBにリストとして登録できるようにしました。ドライバーが乗客のリストを取得するというところまでは、実装が間に合いませんでした。

デモ

youtu.be

反省点

今回は、チームリーダーとしてハッカソンに参加させていただきました。iOSAndroidを分けるという方法をとったことで、UIやロジックの共有がうまくいかなかったところが一番の反省です。特に、iOS側のメンバーに対してバックエンドでどのようなことをしているのか、適宜共有できていなかったことが悔やまれます。一方で、チームメンバーの理解がとても早く、チーム開発自体は円滑に進んだと感じています。私自身としましては、月並みではありますが、A-PxLでの活動や今までのチーム開発でのgitを使った経験を活かし、チームメンバーをサポートできたことは一つ嬉しい瞬間でした。また、チーム開発としての旨味を出すために、どのようにタスクを振るのか、どこまで自分が実装するべきかなど、今まであまり意識しなかったことを考えるきっかけにでき、一つ成長できたように思います。

今までのハッカソンでは、先輩方にたくさん助けていただきながらの実装が多く、全体を俯瞰してアプリケーションを実装するという経験がありませんでした。その結果、今回のハッカソンではガバガバな設計をしてしまい、後半かなり苦しむことになりました。皮肉にも去年の学祭ハッカソンでは、クリーンアーキテクチャを用いた開発をしていたので、先輩方の偉大さを痛感しました。今後は、設計について意欲的に学びたいと考えています。

反省は非常に多いですが、チームメンバーと楽しく開発できたことが一番大きな収穫です!一緒に開発してくれてありがとう!

反省を活かして、これからも精進したいと思います! ここまで読んでいただきありがとうございます!

所属サークル

https://twitter.com/aizu_PxL

私のTwitter

https://twitter.com/AHMOS_HMD

私のgithub

ahmos0 (HAMADA) · GitHub

UnityとAWS Cognitoでサインアップしてみた話

はじめに

こんにちは、会津大学学部二年のHAMADAです。もう今年も残り11ヶ月ということで恐ろしさを感じている今日この頃です。 今年は、毎月技術ブログを2本書きたいと思っているので、来月も頑張ります。 さて今回はUnityとAWS Cognitoのサインアップの話です。

目的と動機

AWSを触る機会が多いですが、A-PxLの活動には今のところ活かせていないので、何かできないかなと思いながら取り組んでみました。サインイン等はうまく活かせば、オンラインゲームやXRのアプリケーションに使えそうなのでCognitoを使います。

使用技術

  • Unity 2022.1.0.f1

    最近触っていなかったので、ただでさえ出来ないのによりひどくなっていました…

  • AWS

    Azureや GCPも触ってみたい気持ちがあります。今回は以下のものを使いました。

    • AWS Cognito

      サインアップ、サインイン等ができるもの。つまりユーザごとの管理ができる(他のAWSサービスとの連携) サードパーティのサインインの使用もできる。

    • (AWS CDK)

      前回のブログの環境を流用しました。一部CognitoをCDKから作成したもののコードのみ記載します。

やったこと

  • Unityでプロジェクトを作成し、nugetからパッケージをダウンロードしてインポート
  • ボタン等を作成
  • コードの作成
  • 実行してみる。

Unityプロジェクトのセッティング

nugetからパッケージをダウンロードしてインポート

ここ(https://www.nuget.org)から以下のパッケージのインポートする

  • AWSSDK.Core
  • AWSSDK.CognitoIdentity
  • AWSSDK.SecurityToken
  • AWSSDK.CognitoIdentityProvider
  • AWSSDK.Extension.CognitoAuthentication

拡張子を.nupkgから.zipに置き換えたのち、zipを解凍します。

その中のlib以下にあるnet45をそのままUnityに入れます。

この時、UnityにはAssetの下にPluginsフォルダを作成し、その中に入れます。

導入するパッケージには依存関係があるので、一つずつ入れていくときにerrorが出ますが、全て入れると解決できます。

怖い話ですが、errorが消えない場合でもUnityを再起動したら、errorが消えたのでうまくいかない場合は試してみてください。

ボタン等の作成

こんな感じになるようにButtonとInputFieldを作成します。

今回はTextMeshProを使っています。

コードを作成

CDKでUserPoolを作成

先にCDKでUserPoolを作成するコードを紹介します。

userPool := awscognito.NewUserPool(stack, jsii.String("UserPool"), &awscognito.UserPoolProps{
        UserPoolName:      jsii.String("userpool"),
        SelfSignUpEnabled: jsii.Bool(true),
    })

awscognito.NewUserPoolClient(stack, jsii.String("sample-client"), &awscognito.UserPoolClientProps{
        UserPoolClientName: jsii.String("app-client"),
        UserPool:           userPool,
    })

前回のブログのコードに上記のように追記することで、今回必要な環境が構築できます。

CDK側からするとemail等がなくても登録できるように設定できます。

SelfSignUpEnabledをTrueにすることで今回やりたいことがうまくいきます。

また、アプリケーションクライアントもCDK側から作成しています。

Signupするためのコード

using System;
using System.Collections.Generic;
using UnityEngine;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
using TMPro;

public class Signup : MonoBehaviour
{
    public TMP_InputField clientidField;
    public TMP_InputField usernameField;
    public TMP_InputField passwordField;

    public void OnClick()
    {
        var client = new AmazonCognitoIdentityProviderClient(null, Amazon.RegionEndpoint.APNortheast1);
        var signup_request = new SignUpRequest 
        {
            ClientId = clientidField.text,
            Username = usernameField.text,
            Password = passwordField.text,
        };

        try
        {
            var result = client.SignUp(signup_request);
            Debug.Log(result);
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }
    }

}

AmazonCognitoIdentityProviderClient で新しくクライアントを作成します。

SignUpRequest でサインアップの登録用の情報を入れます。ここではemailなどの属性を指定することもできます。

client.SignUpで実際に登録します。

このコードをButtonに付けて、それぞれのInputFieldにUIのInputFieldを登録していきます。

Buttonのinspectorにある、On Click()にButtonを登録。Fuctionには、Signup.OnClickを設定します。

実行してみる

Unityで再生ボタンを押して実行します。

clientIDには、アプリケーションクライアントのIDをいれ、usernameには適当な名前を、passwordも自分で設定して入力します。その後SignUpボタンを押すと作成できます!

こんな感じに作られます。

感想と展望

AWS Cognitoの最もシンプルな使い方であろう方法をUnityで試してみました。Cognito自体よりもUnityや.NETのパッケージのセッティングで今回は苦しんだのかなと思います。EmailやSMSの認証を設定していないのでセキュリティの問題は大きくありますが、簡単に実験的にやる分にはシンプルでいいのかなと思います。実運用向きでは絶対ないです。Unityに久しぶりに触れられたので収穫としては満足です!ここまでお付き合いいただきありがとうございます!何かの参考になれば幸いです!

所属サークル

A-PxL (@aizu_PxL) / X

私のTwitter

HAMADA (@AHMOS_HMD) / X

私のgithub

ahmos0 (HAMADA) · GitHub

参考リンク

hakase0274.hatenablog.com

AWS CDK V2をGoで書いてみた ~DynamoDBとAppSyncを添えて~

はじめに

こんにちは、会津大学学部二年のHAMADAです。今年も始まってもう少しで1ヶ月ですね。 最強寒波も来るらしいですが、体調にきをつけて今年も頑張りたいと思います。 さて今回は AWS CDK をGoで書いてみた話です!!

目的と動機

最近は、Alche株式会社インターンをさせていただいています!その中でAWSを学んだり、使ったりする機会が多いのでそのアウトプットとして記事を書きます。GraphQL自体は興味はあったものの使ったことがなかったので、ある程度慣れてきたAWSを使って勉強できる!という気持ちもあり、掘りさげて取り組もうと思いました。

使用技術

  • Go

    ここのところよく使う言語。最近関数型に興味がありますが、Goに甘えてしまう。

  • AWS

    Amazon Web Service 本当に色々できる(知らないこといっぱい)今回使ったのは以下

    • AWS CDK

      Cloud Development Kit プログラミング言語を用いてAWSのリソースを定義しあれこれできる代物

    • AWS AppSync

      GraphQLを用いてAPIのマネジメントができる代物

    • AWS DynamoDB

      NoSQLのデータベース

    • AWS IAM

      権限まわりを操作できる代物

やったこと

  • とりあえず、CDKプロジェクトを構築する
  • スタックを作成
  • DynamoDBを作成
  • AppSyncを作成し、クエリを投げられるようにする。

CDKプロジェクトを構築

CDKのinstall

AWS CDKのコマンドラインツールに cdk というものがあります。これ自体はnpmで提供されています。以下のコマンドを実行します

$ npm install -g aws-cdk
...
$cdk --version
2.60.0 (build 2d40d77)

プロジェクトの作成

以下のコマンドを実行すると、GoのCDKプロジェクトが作成できます。

$ mkdir aws-cdk-go
$ cd aws-cdk-go
$ cdk init --language=go

プロジェクト内には、次のファイルが作成されます。

- README.md            
- .gitignore           
- aws-cdk-go.go       
- aws-cdk-go_test.go  
- cdk.json             
- go.mod               

不要な依存関係を削除するためにgo mod tidyを実行しておきます。

スタックを作成

aws-cdk-exp.go には自動でコードが生成されますが、一度消して簡単なコードを書き直します。

package main
import (
    "github.com/aws/aws-cdk-go/awscdk/v2"
    "github.com/aws/jsii-runtime-go"
)

func main(){
    app := awscdk.NewApp(nil)
    //stackの作成
    //stack := としているがこれは後で使うからで、このままではerrorを吐く
    stack := awscdk.NewStack(app, jsii.String("AwsCdkGo"), &awscdk.StackProps{})
    app.Synth(nil)
}

空のスタックをawscdk.NewStack で作成しています。

ここで一度デプロイしてみます。

go mod tidy が必要であれば実行しておきます。

以下のようにコマンドを実行します。

$ cdk bootstrap //最初のデプロイの前だけでOK
$ cdk deploy 

bootstrapはAWS特定の環境 (アカウントとリージョン)AWS CloudFormation にテンプレートをデプロイすることです。これをしないとdeployで必要だという旨のエラーが出る。

こんな感じでWebコンソール上に作成することができました。

DynamoDBを作成

DynamoDBのテーブルを作成します。基本的には、先ほどのスタックのコードに追加していきます。

package main
import (
    "github.com/aws/aws-cdk-go/awscdk/v2"
    "github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb"
    "github.com/aws/aws-cdk-go/awscdk/v2/awsiam"
    "github.com/aws/jsii-runtime-go"
)
func main(){
    app := awscdk.NewApp(nil)
    //stackの作成コード
    //DynamoDBのテーブル作成
        //table := としているが後で使うからで、このままではerrorを吐く
    table := awsdynamodb.NewTable(stack, jsii.String("demo-table"), &awsdynamodb.TableProps{
        TableName: jsii.String("booktable"),
        PartitionKey: &awsdynamodb.Attribute{
            Name: jsii.String("id"),
            Type: awsdynamodb.AttributeType_STRING,
        },
        BillingMode: awsdynamodb.BillingMode_PAY_PER_REQUEST,
        Stream:      awsdynamodb.StreamViewType_NEW_IMAGE,
    })
    //テーブルの権限を作成
    tablerole := awsiam.NewRole(stack, jsii.String("dynamodb-role"), &awsiam.RoleProps{
        AssumedBy: awsiam.NewServicePrincipal(jsii.String("appsync.amazonaws.com"), &awsiam.ServicePrincipalOpts{}),
    })
    tablerole.AddManagedPolicy(awsiam.ManagedPolicy_FromAwsManagedPolicyName(jsii.String("AmazonDynamoDBFullAccess")))
    app.Synth(nil)
}

awsdynamodb.NewTable で新しくテーブルを作成できます。

引数は順にstack、作成するリソース名、プロパティとなっています。

TableNameは、作成するテーブル名。

PartitionKeyは、データがどのパーティションに保存されるか決めるもの。

BillingModeは、DynamoDBの課金方式を決めています。

Streamは、DBに変更があったときに変更情報を保存しておくもの。

ここで一度デプロイすると、

きちんとtableが作成されています。

AppSyncを作成しクエリを投げる

この手順通りにmain関数にコードを追記していきます。

"github.com/aws/aws-cdk-go/awscdk/v2/awsappsync" をimportします。

まず、appsyncを作成します。

//mainの中に追記していく
//api := としているが後で使うからで、このままではerrorを吐く
api := awsappsync.NewCfnGraphQLApi(stack, jsii.String("booksApi"), &awsappsync.CfnGraphQLApiProps{
        Name:               jsii.String("books-api"),
        AuthenticationType: jsii.String("API_KEY"),
})
awsappsync.NewCfnApiKey(stack, jsii.String("BooksApiKey"), &awsappsync.CfnApiKeyProps{
        ApiId: api.AttrApiId(),
})

awsappsync.NewCfnGraphQLApi で新しくGraphQLApiを作成します。

引数はDynamoDBの時と同様です。

Nameは、api名。

AuthenticationTypeは、権限の認証方法。

awsappsync.NewCfnApiKeyAPI KEYを作成します

引数は他と同様です。

ApiIdは、APIに割り当てられた識別子。

次に、作成したDynamoDBをAppsyncのデータソースに割り当てます。

//mainの中に追記していく
//Ds := としているが後で使うからで、このままではerrorを吐く
Ds := awsappsync.NewCfnDataSource(stack, jsii.String("DataStore"), &awsappsync.CfnDataSourceProps{
        ApiId: api.AttrApiId(),
        Name:  jsii.String("BookDataSource"),
        Type:  jsii.String("AMAZON_DYNAMODB"),
        DynamoDbConfig: awsappsync.CfnDataSource_DynamoDBConfigProperty{
            TableName: table.TableName(),
            AwsRegion: stack.Region(),
        },
        ServiceRoleArn: tablerole.RoleArn(),
    })

awsappsync.NewCfnDataSource で新しくデータソースを作成します。

引数は他と同様です。

Typeは、データソースが何かを指定できます。今回はDynamoDBですが、Lambda等でもOK。

DynamoDbConfigは、DynamoDBへの接続を定義しています。

ServiceRoleArnは、Roleを参照しています。

その次は、スキーママッピングテンプレートを別ディレクトリで定義し、それをとってきます。

   //mainの中に追記していく
    //schemaを、ファイルからとってくる
    //def := としているが後で使うからで、このままではerrorを吐く
    def, err := os.ReadFile(filepath.Join(".", "resource", "schema.graphql"))
    if err != nil {
        fmt.Println("failed to load graphql definition " + err.Error())
    }
 
    //schemaを定義
    //schema := としているが後で使うからで、このままではerrorを吐く
    schema := awsappsync.NewCfnGraphQLSchema(stack, jsii.String("GraphSchema"), &awsappsync.CfnGraphQLSchemaProps{
        ApiId:      api.AttrApiId(),
        Definition: jsii.String(string(def)),
    })

    getitem, err := os.ReadFile(filepath.Join(".", "resource", "getitem.vtl"))
    if err != nil {
        fmt.Println("failed to load  getitem.vtl " + err.Error())
    }
    putitem, err := os.ReadFile(filepath.Join(".", "resource", "putitem.vtl"))
    if err != nil {
        fmt.Println("failed to load  putitem.vtl " + err.Error())
    }

./resouce/schema.graphqlに定義しておいた、スキーマをとってきてawsappsyncに渡す。

awsappsync.NewCfnGraphQLSchema で新しくスキーマを作成します。

引数は他と同様です。

Definitionは、スキーマの中身を定義。

schema.graphqlの中身

schema {
    query: Query
    mutation: Mutation
}

type Query {
    getPost(id: ID): Post
}

type Mutation {
    addPost(
        id: ID!
        author: String!
        title: String!
        content: String!
        url: String!
    ): Post!
}

type Post {
    id: ID!
    author: String
    title: String
    content: String
    url: String
    ups: Int!
    downs: Int!
    version: Int!
}

getitem.vtlの中身

{
    "version" : "2017-02-28",
    "operation" : "GetItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
    }
}

putitem.vtlの中身

{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
    },
    "attributeValues" : {
        "author" : $util.dynamodb.toDynamoDBJson($context.arguments.author),
        "title" : $util.dynamodb.toDynamoDBJson($context.arguments.title),
        "content" : $util.dynamodb.toDynamoDBJson($context.arguments.content),
        "url" : $util.dynamodb.toDynamoDBJson($context.arguments.url),
        "ups" : { "N" : 1 },
        "downs" : { "N" : 0 },
        "version" : { "N" : 1 }
    }
}

このチュートリアルスキーママッピングテンプレートを流用しました。

チュートリアル:DynamoDB リゾルバー

最後に、QueryとMutationを投げるリゾルバーを作成。

//mainのなかに追記
//Queryを投げられるようにする
    awsappsync.NewCfnResolver(stack, jsii.String("GetResolver"), &awsappsync.CfnResolverProps{
        ApiId:                   api.AttrApiId(),
        TypeName:                jsii.String("Query"),
        FieldName:               jsii.String("getPost"),
        DataSourceName:          Ds.Name(),
        RequestMappingTemplate:  jsii.String(string(getitem)),
        ResponseMappingTemplate: jsii.String(`$util.toJson($ctx.result)`),
    }).AddDependency(schema)

    //Mutationを投げられるようにする
    awsappsync.NewCfnResolver(stack, jsii.String("AddResolver"), &awsappsync.CfnResolverProps{
        ApiId:                   api.AttrApiId(),
        TypeName:                jsii.String("Mutation"),
        FieldName:               jsii.String("addPost"),
        DataSourceName:          Ds.Name(),
        RequestMappingTemplate:  jsii.String(string(putitem)),
        ResponseMappingTemplate: jsii.String(`$util.toJson($ctx.result)`),
    }).AddDependency(schema)

awsappsync.NewCfnResolver は新しくリゾルバーを作成します。

TypeNameは、Query,Mutationなどを指定できます。

FieldNameは、スキーマに含まれているフィールドを指定します。

DataSourceNameは、今回は先ほど定義したデータソースの名前をとってきます。

RequestMappingTemplateは、putiem、getitemから指定しています。

ResponseMappingTemplateは、context.resultをJson形式にして返しています。

Webコンソールで実行できるようになりました。

まず、mutationで情報を追加。

その後、queryで情報を取得。

無事にDynamoDBとAppSyncを接続し実行するためのCDKを作成できました!!!

感想と展望

AWS CDK V2をGoで書く場合に参考になる公式ドキュメントが少なく、割と苦労しながらの実装になりました。vscodeエスパーしながら進めていたので、vscodeの補完能力には感謝しかありません。GraphQLに関しては、まだまだ知識が足りていないのでGraphQL単体でも学びたいと思っています。他の人のコードを読んだり、探したりすることが今までよりも多く学びが深まったことは大きな収穫だと思います。何かの参考になれば幸いです。ここまでお付き合い頂きありがとうございました!

所属サークル

A-PxL (@aizu_PxL) / Twitter

私のTwitter

HAMADA (@AHMOS_HMD) / Twitter

私のgithub

ahmos0 (HAMADA) · GitHub

Alche株式会社

alche.studio

参考リンク

maku.blog

pkg.go.dev

HAMADAの2022年振り返り

こんにちは、会津大学学部二年のHAMADAです。2022年も末ですが、皆様いかがお過ごしでしょうか?私は実家でぬくぬくと引きこもり生活をしています…

さて今回は、2022年の振り返りをしていきたいと思います。ポエムっぽいかもですがよろしければお付き合い下さい。

1月

触れた技術

  • Unity
    • 当時、知っている技術がUnityぐらいで縛られていた感じがあります。
  • C#
    • Unityにしか使っていなかったので、言語自体を真面目に学ぼうと思っていました。
  • git
    • 習ってから、まともに使っておらず、git怖いって感じでした。

取り組んだこと

振り返り

2021年の12月に開催されたZliの技術座談会に参加したことで、大きな刺激を受けてモチベーションが向上し、とりあえず何かやろうとしていました。この時期は、周りの人たちは技術に関することが全てできるように感じていて、こんな風に強くなりたいという気持ちと自分ができるようになるのかという不安が同居していました。

実際には、自分が知らないことが多すぎて、周りの人たちが全知全能であるように感じていたようで…自分に感知できない領域があるとそういうふうに感じてしまっていたわけです。そんなことはないと気付くまでには、長い時間がかかったように思います。

最初からできる人なんていないので、楽しく取り組むことが一番大事なのだと思います。

ただ、1月はがむしゃらで何をするべきか定まっていなかったので迷走していたような気が…

2月

触れた技術

  • Unity
    • Rigidbody(重力だけ)を自分で実装してみる

出来事

  • 寮から引っ越し (サーバーサイドに興味を持つきっかけになる)

振り返り

この時期は、引っ越しが忙しくて特に何かきちんとやった記憶はないのですが、A-PxLの卒業追LTで何か発表したくて、Unityで重力を自分で実装しました。授業で習った力学や微積分(正直高校の時の知識で十分でした)を意識しながら実装しました。大学で数学や物理を習うことが、どのように活かせるのかを意識したくて取り組んだ記憶があります。

この時期から、ゲームを自分で作ったりすることよりも、ゲームエンジンそのものや、物事の根本に興味があって、それに関係した開発をしたいと意識するようになりました。

性分的にも、地道に着実に物事を進めていくことが好きなので、自分自身の軸として設定することに躊躇いはありませんでした。(実際に軸として考え出すのは、もう少し先)

3月

触れた技術

  • C#
    • 教える立場になるのに何も理解してなかったので、必死に勉強する

出来事

  • お風呂の水位メーターシステムを作りたいと思う

振り返り

引っ越し先の家には、お風呂が溜まったら自動で止めて音が鳴るといった大層なものがついていないので、せっかくやったら自作しようとふと思い立ちました。

ラズパイを使って、お湯が溜まったらLINEかなにかに通知してくれるといったシンプルなシステム。

しかし、当時の自分には電子工作の知識も、LINE Botなるものがあるという知識すらありませんでした。それどころか、バックエンド、フロントエンド、レイヤーの話など今では普通に使う言葉すら知りませんでした。

本当に、技術の話をされると異国の言語を聞いているような感じでした。技術は好きだったので、ワクワクはしていましたが、知らないことに恥ずかしさを覚えていた記憶があります。

ということで、ぼんやりと今年の目標として設定した上でしどろもどろしていました。

4月

触れた技術

出来事

  • Zliの技術座談会(2回目)に参加
  • サークルの新入生説明会

振り返り

2021年の座談会では、ぼんやりと技術について話を聞いていたのですが、この時は水位メーターを作ることを軸に話を聞いていました。

ここでバックエンドに出会います。友人がつよつよだったのもあり足を踏み入れやすかったのかもしれません。ありがとうございます。

この時は、技術力的に一番書いてきたC#でバックエンドを書こうと思っていました。というよりは、新しい言語の習得は地に足がついていないような感覚で嫌だったのかもしれません。

今やっている言語もまともに書けへんのに…って感じですね。

兎に角、ここでだんだん方向性が決まってきました。新学期なのもあってやる気は抜群でした。

5月

触れた技術

  • Blazor
    • 全然できなくて挫折
    • Azureでも苦しむ
  • Node.js
  • LINE Bot

出来事

  • Aizu Hack(勉強会付き学内ハッカソン)で LINE Botコースに参加
  • 先輩に誘われてIVRCにメンバーとして参加

振り返り

5月は手始めに、”C# バックエンド”で検索してみて出てきたBlazorに挑戦してみましたが、基礎知識が足りてないせいで全く分かりませんでした。ただ、自分が悪いのがわかっているので、学習意欲が下がることはなく、一度C#にこだわらずに取り組んでみようと思いました。機会があれば、今の力でチャレンジしてみたい。

その後、Aizu HackでNode.jsを学んで、 LINEBotの開発を始めました。ぼんやりとですが、バックエンドの正体が見えてきました。バックエンド完全に理解した!!!まではもう少しといった感じでした。

IVRCは今年の一大イベントでした。この頃は、アイデア出しをしていた記憶があります。話したことのない先輩たちと、技術を含め色々な話ができるようになったきっかけなので今年の過ごし方にすごく影響がありました!!

6月

触れた技術

  • Node.js
  • LINE Bot

出来事

  • はてなブログ開設
  • IVRC書類選考が通る
  • 技育博にいけなくなる
    • コロナっちが大学で流行っていて、部活が禁止になったせい(無力さを感じた)

振り返り

どうにかアウトプットをしたくてブログを始めました。2021年にもブログを書く機会はあったのですが、難しいことを書かなければいけないという先入観があって書けずにいました。

やったことの振り返りとして、言葉にまとめることは自分の理解度チェックに非常に効果的だと気づいてからはブログへの抵抗感がなくなりました。

また、ブログは開発の起点になるので、ブログを書くことをTwitterで宣言すれば締め切りを設定したブログ駆動開発ができます。自分を追い込むこともできます!!!

些細なことを書いたとしても、怒る人はいません。技術ブログ強くお勧めします!!!

7月

触れた技術

  • Node.js

出来事

  • LT用の Joy-conリモコンについてブログを書いた

Joy-conでLT用のスライドリモコンを作った話 - HAMADAの語り草

  • 応用情報申込忘れる

振り返り

役に立つものの実装がしてみたくてGamepad APIを叩いてJoy-conでスライドリモコンを作ってみました。生きたプログラムを書くことができたような感じがしてすごく嬉しかったのを覚えています。

この辺りから、技術の学び方、使い方が自分なりに固まってきたように思います。

なにをしたら良いかわからず、しどろもどろしていた頃と比べて、明確に目標を定めて取り組めるようになったのが功を奏したのだと思います。

締切はちゃんと確認します。

8月

触れた技術

出来事

  • Aizu Hack発表
  • LINEからLチカのブログを書く

LINEからのメッセージでラズベリーパイを使ったLチカをした話 - HAMADAの語り草

  • ARに入門した話をブログに書く

UnityでARに入門した話 - HAMADAの語り草

  • 高校の時の先輩に言われて、技術が好きだと自覚する

振り返り

今まで、静的型付けの言語ばかり書いてきたのでバックエンドも静的型付けで書きたいなという思いからGoに入門しました。

Goが割と書きやすい言語だったのもあるかもしれませんが、色々な経験を踏まえて理解の速度が今までよりも上がった気がしました。

積み重ねは大事だと実感したと同時に、もっといろんなことに触れたいとモチベーションが高まったのを覚えています。

大事な時期にUnityやハード面でIVRCのチームにほとんど貢献できなかったので、申し訳なかったなと思います。一方で自分以外のメンバーは本当にレベルが高くて負けずに頑張りたいという刺激になりました。来年もIVRCにチャレンジしたいと考えているので、来年は是非ともチームに大きく貢献したいと思っています。

9月

触れた技術

  • Firestore

出来事

  • なにもできなくなった
  • IVRC Leap Stage 進出

振り返り

Aizu Hackで作成したLINE BotをデプロイしたくてFirestoreを触ってみました。友人が作成したコードを移行し直すということで、だいぶ苦しみました。正直なところ実装は諦めました(泣)

この辺りで、設計を意識し始めました。実務などにおいて、どんなことを意識しているのかが気になって、インターンに行くことを強く意識するようになりました。

一方で、トラブルが立て続けに起こり外的な要因で、メンタルが少し落ち込んでしまいました。普段は基本的に元気いっぱいなので、反動でなにもできなくなって寝てばかりいました。

2020年2021年も、9月に色々なことが失速していたので、心が疲れる時期なのかもしれないと自覚することができたのは大きな発見でした。

10月

触れた技術

  • Go
  • Unity
    • 学祭の開発

出来事

クリーンアーキテクチャについて話を聞いたんです。 - HAMADAの語り草

振り返り

メンタルは新学期と共に元に戻り、普段の生活を再開することができました。

一般の方に制作物を体験してもらえる機会を得ることができ、生の感想をもらうことができただただ嬉しかったです。

バックエンドの技術に関して、夏休みの間はインプットが多かったのですが、学祭ハッカソンでは先輩方に教えていただきながら、アウトプットをすることができました。

学祭ハッカソンの前と後では、技術に対する考え方や能力が一つステップアップできたように感じます。

ハッカソンは、自分より技術力が高い人と一緒に何かできる貴重な機会でもあるので、成長の場として価値があるように感じました。来年は学外のハッカソンにも出てみたいです。

11月

触れた技術

  • Arudino
  • Go
  • AWS

出来事

AWSのハンズオンをGoで書いて入門した話 ~翻訳APIを作ったよ~ - HAMADAの語り草

  • IVRC 審査員特別賞受賞
  • こでらんに文化祭(ラジオの公開収録) お笑い芸人のたんぽぽさんとお話しさせていただいた
  • A-PxLで運営学年になる

振り返り

テレビで見ていた方と直接お話をさせていただいて、サークルの作品を紹介できたことは何事にも変えがたい経験になりました!!!当日は緊張こそしましたが、言いたいことを話すことができ、聞いてくれた友人や先輩にもよかったと言ってもらえたので、すごく自信にも繋がりました。

サークルの運営方法や方針を考える時期でもあったので、思っていることを発信、言語化の難しさを感じた1ヶ月でした。

AWSに触れることができたのは大きかったです。クラウドサービスなどには割と苦手意識があったので、それを払拭することができたのは大きな収穫です。

12月

触れた技術

出来事

  • AWSで記事を書く

AWS CLIを簡単に使えるようにしたのでメモを残したい - HAMADAの語り草

お風呂の水位センサーを作るために、ラズパイからLINEに通知できるようにしてみた話 - HAMADAの語り草

githubの草を取得してみた話 (エンジニア版たま○っちを作る準備) - HAMADAの語り草

振り返り

12月は、今年一年の集大成という感じです。完全ではないですが、技術に能動的に取り組むきっかけになった水位メータを実装できました。また、今まではアイデアが一人歩きして技術的に不可能だったことも、手を伸ばせばできるようなレベル感まできたように思います。(もっとも、それは泥臭い個人開発の域を出ていない。とりあえず動く程度)

振り返っての感想

昨年から成長したことは、気になることに迷わず飛び込んでみることができるようになったことです。短いスパンだけでなく、長いスパンで物事を見ると変わっているという見方を私は大事にしていますが、今年はそれがより顕著な年になったのかなと思います。

周りに恵まれて、今年一年様々な活動をすることができました。皆様本当にありがとうございました。

また、去年の私のように技術に触れたいけど、何をやっていいのかわからないという人が一歩目を踏み出せるように、これからも色々なことを発信していきたいと思います。

一年間ありがとうございました!!!良いお年を〜

所属サークル

A-PxL@C101土曜西こ44b (@aizu_PxL) / Twitter

私のTwitter

HAMADA (@AHMOS_HMD) / Twitter

私のgithub

ahmos0 (HAMADA) · GitHub