しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

RubyでOpenAI APIを使ってみる

Rubyのライブラリを使ってOpenAIのAPIを使ってみました。

まずは環境変数にOpenAIのAPI Keyをセットします。
キーは事前にOpenAIのサイトから取得しておきます。

export OPENAI_API_KEY=sk-XXXXXXX

次はGemfileでライブラリを追加します。

gem "openai"

最後に下のような実装でAPIを呼び出します。

openai_client = OpenAI::Client.new(api_key: "sk-XXXXXXX", default_engine: "text-davinci-003")
result = openai_client.completions(prompt: "夕食のおすすめはある?", max_tokens: 100)

呼び出し結果は下の通りです。
finish_reason="length"なのでtokensが小さすぎた事が分かります。
こういう時はmax_tokensを大きくして再度APIを呼び出します。
ただ長くすると返答に時間がかかるので注意が必要です。

PreferenceFragmentCompatで設定画面を作成する

PreferenceFragmentCompatという設定画面作成用のコンポーネントを試してみました。

まずはbuild.gradleでライブラリを追加します。

implementation 'androidx.preference:preference:1.2.0'

次は設定画面用のXMLを作成します。
res/xmlにpreferences.xmlというファイルを作成して下のようにします。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="設定1">
        <CheckBoxPreference
            android:key="notifications"
            android:title="通知を有効にする"
            android:defaultValue="true" />
    </PreferenceCategory>
</PreferenceScreen>

次は下のように設定画面用のFragmentを作成します。

class MySettingFragment: PreferenceFragmentCompat() {
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.preferences, rootKey)
    }
}

これでアプリを起動すると下のように設定画面が表示されます。
ここでチェックのON/OFF切り替えをするとSharedPreferencesにデータが保存されます。

保存されたデータは下のように取得する事ができます。
プロパティーの名前はxmlandroid:keyで指定したものになります。

val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val value = preferences?.getBoolean("notifications", false)

設定はチェックボックス以外にも文字入力・リストなど様々なものがあります。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="設定1">
        <SwitchPreferenceCompat
            android:key="dark_mode"
            android:title="ダークモード"
            android:summary="ダークモードを有効にする"
            android:defaultValue="false" />

        <EditTextPreference
            android:key="username"
            android:title="ユーザー名"
            android:dialogTitle="ユーザー名を入力してください"
            android:summary="ユーザー名はアプリ内で表示されます"
            android:defaultValue="user123" />

        <ListPreference
            android:key="language"
            android:title="Language"
            android:summary="Select your preferred language"
            android:entries="@array/entries"
            android:entryValues="@array/values" />
    </PreferenceCategory>
</PreferenceScreen>

ListPreferenceで使う配列はres/arrays.xmlなどで下のように定義する必要があります。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="entries">
        <item>entry 1</item>
        <item>entry 2</item>
        <item>entry 3</item>
    </string-array>
    <string-array name="values">
        <item>0</item>
        <item>1</item>
        <item>1</item>
    </string-array>
</resources>

リストは下のようにアラート上で選択します。

ChatGPTを試してみたメモ書き

最近1ヶ月くらいプログラミングをする際にChatGPTを使ってみたのでメモ。

使い方

  • Androidアプリを作るときやRuby on Railsでの開発時に質問をしている。
  • 「このViewのここだけ角丸にしたい」「このコンポーネントの背景色を変えたい」みたいにどのプロパティーやメソッドを使えば良いかを聞くのが多い
    不慣れなプログラミング言語だととても便利。
    最近はChatGPTで聞いて駄目だったらググる事が増えてきた。
    慣れている言語だと上のような事は分かるのであまり使わない。
    ただ、始めたばかりの言語だと回答結果の内容も分からない事がある。ある程度慣れた言語で使うのが合ってる気がする。
    時々間違えたり古いAPI使うけど割とすぐ気がつけるのでそこまで困らずに使えている。
    英語の情報も使っているからか日本語情報がないものも教えてくれる。
  • 「XXというクラスの使い方を教えて」「XXって言うのは何?」みたいに概念やクラスについての質問もしている
    公式ドキュメントに比べると短く説明してくれるので分かりやすい。
    ここでぼんやりと理解してから公式ドキュメントを読むと理解が早くて楽。
  • メソッドやクラスを丸ごと書いてもらう事はほとんどない
    サンプルコードを書いてもらう事はあるけどメソッド丸ごと実装してもらうとかはあまりない。
    使うクラスやプロパティーが分かれば実装はできるのでそんなに聞かない。
    クラス丸ごと実装はできればやってもらいたい。
    ただ説明が大変なのと間違っていた時の直すのが大変そうなので使っていない。
    ドキュメントをしっかり書くきっかけにはなるかも。
  • GitHub Copilotは使い始めたけど便利そう

良いところ

  • ピンポイントでほしい情報を出してくれるのが助かる
    ググった場合、1ページの情報が多いのでピンポイントで探すときは少し時間がかかってしまう事がある。
  • 質問する時に一度言語化するので自分自身が何をしたいかの整理できている気がする
  • UIがシンプルなので気が散らない
  • サンプルコードの中で分からない事があれば追加で質問できる

Android StudioにGitHub Copilotを入れてみる

Androidアプリ開発GitHub Copilotを試してみました。
以前Xcodeで試した記事は下URLです。

GitHub Copilotを使って自動コード補完 - しめ鯖日記

まずは下の公式サイトからGitHub Copilotを有効にします。

docs.github.com

GitHub Copilotを有効にしたらAndroid Studioで設定を行っていきます。
設定は下ページを参考にしました。

docs.github.com

まずは設定のPluginsからGitHub Copilotを探してインストールします。
プラグインの説明を見る限り、現在はGPT-3を使っているようです。

プラグインを入れたらToolsのGitHub Copilotからログインします。

ログインはAndroid Studio上のDevice Codeを下URLに入力する形です。

Sign in to GitHub · GitHub

これで設定が完了したので実際に使っていきます。
試しにMyClassというクラスを作ると下のようにサジェストを出してくれます。
タブを押すとサジェストが反映されます。
Option+[とOption+]で別の候補を出してくれます。

次はメソッドを作ってみます。
試しにsaveFileという名前のメソッドを作ったら下のような形で補完してくれました。

fun saveFile() {
    val myFile = File("myFile.txt")
    myFile.writeText("Hello World!")
}

下のようにコメントを追加するとそれを元に候補を出してくれます。

日本語でコメントを書いたら下のように補完をしてくれました。
applyとcommit使っている所などは直す必要がありますがそれ以外はほぼそのまま使えそうでした。

// myTextViewというIDのTextViewの内容を取得してSharedPreferencesに保存する
fun saveTextView() {
    val myTextView = findViewById<TextView>(R.id.myTextView)
    val myText = myTextView.text.toString()
    val pref = getSharedPreferences("myPref", Context.MODE_PRIVATE)
    val editor = pref.edit()
    editor.putString("myText", myText)
    editor.apply()
    editor.commit()
    Toast.makeText(this, "Saved", Toast.LENGTH_SHORT).show()
}

RippleDrawableでボタン押下時のようなアニメーションを付ける

RippleDrawableを使ってTextViewなどを押した時にもボタンを押したときのようなアニメーションが出るようにします。

まずはXMLでRippleDrawableを定義します。
res/drawableフォルダの中にtest_drawable.xmlというファイルを作成して下のように記述します。
colorが選択時の色、android:drawableが背景色です。

<?xml version="1.0" encoding="utf-8"?>
<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#ccc">
    <item android:drawable="@android:color/darker_gray" />
</ripple>

作成したらTextViewに適用します。
下のようにTextViewのbackgroundに@drawable/test_drawableをセットします。

その際はandroid:clickable="true"も忘れずに付けます。
これがないとタップ時のアニメーションが表示されません。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".FirstFragment">

    <TextView
        android:id="@+id/my_text"
        android:layout_width="60dp"
        android:layout_height="40dp"
        android:text="TEST"
        android:gravity="center"
        android:background="@drawable/test_drawable"
        android:textColor="@color/white"
        android:clickable="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

起動すると下のように表示されます。
真ん中のTextViewをタップすればボタンのようなアニメーションになります。

ボタンのような選択色にしたい場合、colorを?android:colorControlHighlightにします。

<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item android:drawable="@android:color/darker_gray" />
</ripple>

下のようにshapeタグを使う事で角丸にしたりカラーコードでの色指定ができます。

<?xml version="1.0" encoding="utf-8"?>
<ripple
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#ff0000" />
            <corners android:radius="8dp" />
        </shape>
    </item>
</ripple>

Kotlin上で背景色を利用したい場合、下のようにRippleDrawableを初期化してセットします。

val attrs = intArrayOf(android.R.attr.colorControlHighlight)
val typedArray = requireContext().obtainStyledAttributes(attrs)
val color = typedArray.getColor(0, 0)
val background = GradientDrawable().apply { this.color = ColorStateList.valueOf(Color.RED) }
binding.myText.background = RippleDrawable(ColorStateList.valueOf(color), background, null)
typedArray.recycle()

ここまでRippleDrawableの使い方を見てきましたが、RippleDrawableを使わなくてもforegroundに?attr/selectableItemBackgroundをセットする方法でもアニメーションさせる事は可能です。

<TextView
    android:id="@+id/my_text"
    android:layout_width="60dp"
    android:layout_height="40dp"
    android:text="TEST"
    android:gravity="center"
    android:foreground="?attr/selectableItemBackground"
    android:background="@android:color/darker_gray"
    android:textColor="@color/white"
    android:clickable="true"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

GitHub Copilotを使って自動コード補完

GitHub CopilotというAIが自動でコード補完してくれるサービスを試してみました。

github.com

Xcodeは公式未対応なので、下のプラグインを利用しました。
サードパーティーアプリなので入れる際は会社のセキュリティーポリシー上問題ないかご確認下さい。

github.com

まずはGitHub Copilotを有効にします。
有料ですが無料トライアル期間もあります。

次にCopilot for Xcodeをインストールします。
インストールはBrewを使いました。

brew install --cask copilot-for-xcode

インストールしたらCopilot for Xcodeアプリを立ち上げます。
Node.jsを使うのでインストールしていない場合はインストールします。
インストールをしたらPath to NodeにNode.jsのパスを入れます。

次に設定アプリの「プライバシーとセキュリティー」の「拡張機能」の「Xcode Source Editor」でCopilotを有効にします。

再度Copilot for Xcodeを開き直すと下のような表示になるのでSign inからGitHubにログインします。
もしうまく行かない場合、Xcodeを閉じる・Copilot for Xcodeを開き直すなどの対応をします。

下のように許可を求める表示が出るので許可を押します。

Copilot for XcodeでRefreshを押すと下のようにログイン済み表示になります。

これで設定は完了です。
次はXcodeで実際に使ってみます。

新規プロジェクトを作成して下のようなコードを書きます。

class ViewController: UIViewController {
    func readFile() {
        
    }
}

readFileメソッドにフォーカスをあてた状態でEditorのCopilotのGet Suggestionsを押します。

数秒待つとXcode右下に下のようなコード候補がでます。

許可を押すと下のようにコードが展開されます。
今回はDocumentフォルダのtest.txtファイルの文字を読み込むコードがサジェストされたようです。

class ViewController: UIViewController {
    func readFile() {
        let fileManager = FileManager.default
        let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let fileURL = documentDirectory.appendingPathComponent("test.txt")
        do {
            let text = try String(contentsOf: fileURL, encoding: .utf8)
            print(text)
        } catch {
            print(error)
        }
    }
}

ViewControllerのreadFileの1行上にフォーカスをあてたら下のようにviewDidLoadメソッドを提案されました。

signInWithTwitterという利用頻度が低そうなメソッドの中で使ったら下のようにコメントだけサジェストされました。
もしかするとSwiftではsignInWithTwitterというメソッドがGitHub上になかったからかもしれません。

class ViewController: UIViewController {
    func signInWithTwitter() {
        // ...
    }
}

使ってみた感想ですがよく使われる機能のサンプルコードを探したりするのには便利そうでした。
プラグインがまだうまく動かない事もあったりするので、公式で対応されたらまた試してみようと思います。
それとコメントを入れる事でサジェストが改善されるようなのでこれも今後試していきたいと思います。

参考URL

【Xcode】XcodeでGitHub Copilotを使ってみた - Qiita

GitHub Copilot にいいコードを書いてもらう方法 - GMOインターネットグループ グループ研究開発本部(次世代システム研究室)

【Kotlin】Androidでファイルアプリへの保存・取得を試す

Androidでファイルへの保存・取得を試してみました。

ファイルへの保存するために、まずはregisterForActivityResultでActivityResultLauncherインスタンスを作ります。

val  writeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode != Activity.RESULT_OK)
        return@registerForActivityResult

    result.data?.data?.let { uri ->
        // 保存処理
    }
}

次に下のようにIntentを作ってwriteResultLauncherのlaunchメソッドに渡します。

val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "text/plain"
    putExtra(Intent.EXTRA_TITLE, "ファイル名")
}
writeResultLauncher.launch(intent)

そうするとregisterForActivityResultのコールバックが呼ばれます。
その時、保存先URLが渡されるのでそこにデータを保存します。

val writeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode != Activity.RESULT_OK)
        return@registerForActivityResult

    result.data?.data?.let { uri ->
        contentResolver.openOutputStream(uri)?.use { outputStream ->
            outputStream.write("テスト".toByteArray())
        }
    }
}

ファイルの読み込みもregisterForActivityResultを使います。
今度は読み込んだファイルのURLが渡されるので下のように読み込みます。

val readResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode != Activity.RESULT_OK)
        return@registerForActivityResult

    result.data?.data?.let { uri ->
        BufferedReader(InputStreamReader(contentResolver.openInputStream(uri)))?.use { reader ->
            val stringBuilder = StringBuilder()
            var line = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
            Log.i("Test", stringBuilder.toString())
        }
    }
}

Intentを渡す方法は下の通りです。
今回は読み込みなのでIntentの初期化の引数をACTION_OPEN_DOCUMENTにしています。

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
    addCategory(Intent.CATEGORY_OPENABLE)
    type = "text/plain"
}
readResultLauncher.launch(intent)

Intentを渡すと下のようにファイルを選ぶ画面に遷移できます。