Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Compose Multiplatform で Bluesky のクライアント作ってみた / ...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Yuki Anzai
April 30, 2023
Technology
760
0
Share
Compose Multiplatform で Bluesky のクライアント作ってみた / Bluesky client with Compose Multiplatform
Bluesky/ATProtocol 勉強会#1
https://428lab.connpass.com/event/280610/
Yuki Anzai
April 30, 2023
More Decks by Yuki Anzai
See All by Yuki Anzai
Android Studio Otter の最新 Gemini 機能 / Latest Gemini features in Android Studio Otter
yanzm
0
980
Devin で iOS の PR から Android のコードを生成する / Generate Android code from iOS PR using Devin
yanzm
0
230
AI ツールを活用したコードリーディング - Android の公式サンプル Now in Android のソースコードを読んでみよう - / Code reading with AI tools
yanzm
1
420
EncryptedSharedPreferences が deprecated になっちゃった!どうしよう! / Oh no! EncryptedSharedPreferences has been deprecated! What should I do?
yanzm
0
2.8k
Devinを使ったモバイルアプリ開発 / Mobile app development with Devin
yanzm
0
410
Android Studio の 新しいAI機能を試してみよう / Try out the new AI features in Android Studio
yanzm
0
450
What’s new in Android development tools
yanzm
0
980
Google I/O 2025 Keynote & Developer Keynote Overview
yanzm
0
210
Coding Agent を使って Android アプリを作ってみる / Let's try using coding agent for Android app development
yanzm
0
240
Other Decks in Technology
See All in Technology
AWS認定資格は本当に意味があるのか?
nrinetcom
PRO
1
220
BigQuery × dbtでコスト削減した話
rightcode
0
140
インフラを Excel 管理していた組織が 3 ヶ月で IaC 化されるまで
geekplus_tech
3
190
新規サービス開発におけるReact Nativeのリアル〜技術選定の裏側と実践的OSS活用〜
grandbig
2
190
明日からドヤれる!超マニアックなAWSセキュリティTips10連発 / 10 Ultra-Niche AWS Security Tips
yuj1osm
0
460
数案件を同時に進行するためのコンテキスト整理術
sutetotanuki
2
240
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
10
78k
みんなで作るAWS Tips 100連発 (FinOps編)
schwrzktz
1
220
AI時代に新卒採用、はじめました/junior-engineer-never-die
dmnlk
0
270
NgRx SignalStore: The Power of Extensibility
rainerhahnekamp
0
230
Zero Data Loss Autonomous Recovery Service サービス概要
oracle4engineer
PRO
5
14k
試されDATA SAPPORO [LT]Claude Codeで「ゆっくりデータ分析」
ishikawa_satoru
0
390
Featured
See All Featured
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
110
Into the Great Unknown - MozCon
thekraken
40
2.3k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
210
Statistics for Hackers
jakevdp
799
230k
It's Worth the Effort
3n
188
29k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
1
510
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
190
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.4k
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
64
54k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
370
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
150
Transcript
$PNQPTF.VMUJQMBUGPSNͰ #MVFTLZͷΫϥΠΞϯτ࡞ͬͯΈͨ yanzm
ZBO[NʢΜ͟Ήʣ w !ZBO[NCTLZTPDJBM w ݹʢ͍ʹ͑͠ʣͷ"OESPJEFS w ݄ʹ,PKJSB͞ΜʹটίʔυΒͬͯCMVFTLZ͡Ί·ͨ͠ɻ৽ถͰ ͢ɻ
,PUMJO w ʢͱͱʣ+7.ݴޠ w CFUUFS+BWBతͳ w ͔Β"OESPJEͷެࣜαϙʔτݴޠͷҰͭ
,PUMJO.VMUJQMBUGPSN ,.1 w ,PUMJOͷίʔυΛXFC +T ͱ͔OBUJWF EFTLUPQ ͱ͔ͷόΠφϦʹϏϧυ w ಉ͡,PUMJOͷίʔυͰෳͷ1MBUGPSNʹରԠ
w J04ରԠ w ͨͩ͠ɺ6*ͦΕͧΕͷ1MBUGPSN/BUJWFͳ෦Ͱ࣮
+FUQBDL$PNQPTF w "OESPJEͷωΠςΟϒ6*Λߏங͢ΔͨΊͷ࠷৽ͷπʔϧΩοτ w એݴత6* Row { Icon( imageVector =
Icons.Default.Star, contentDescription = null ) Column { Text( text = "username" ) Text( text = "description" ) } Button( onClick = onClickFollow ) { Text("follow") } }
$PNQPTFGPS%FTLUPQ w ݄ w +FUQBDL$PNQPTFͰ,PUMJO.VMUJQMBUGPSNͷEFTLUPQͷ6*͕࡞ΕΔ
$PNQPTF.VUMJQMBUGPSN6*'SBNFXPSL w IUUQTXXXKFUCSBJOTDPNMQDPNQPTFNVMUJQMBUGPSN w ݄ͷ,PUMJO$POGͰൃද w EFTLUPQ͚ͩ͡Όͳ͘J04 "MQIB ,PUMJO/BUJWF 8FC
&YQFSJNFOUBM ,PUMJO8BTN αϙʔτ
#MVFTLZͷΫϥΠΞϯτΞϓϦΛ $PNQPTF.VMUJQMBUGPSNͰ࡞Δ https://github.com/yanzm/Rayleigh
4UFQϕʔεͷϓϩδΣΫτΛ࡞Δ w +FU#SBJOTͷHJUIVCʹDPNQPTFNVMUJQMBUGPSNͷςϯϓϨʔτ͕͋Δ w IUUQTHJUIVCDPN+FU#SBJOTDPNQPTFNVMUJQMBUGPSNJPTBOESPJE UFNQMBUF
4UFQ௨৴Ͱ͖ΔΑ͏ʹ͢Δ w 4FJVO͞Μ3FUSP fi UΛ͍͚ͬͯͨͲ,.1Ͱ͑ͳ͍ w ,UPS w IUUQTLUPSJP w
,PUMJO$POG`ͷηογϣϯͰग़͖ͯͨ$PNQPTF.VMUJQMBUGPSNͰνϟο τ͢ΔΞϓϦ͕ࢀߟʹͳͬͨ w IUUQTHJUIVCDPNTWUL,$$IBU"QQ w ,UPS ,PUMJOY4FSJBMJ[BUJPOͱ͔
4UFQϩάΠϯը໘ͷ6*Λ࡞Δ
4UFQϩάΠϯը໘ͷ6*Λ࡞Δ w SEQBSUZDMJFOU"QQ1BTTXPSEͰϩάΠϯ͢ΔΑ͏ʹͳ͔ͬͨΒɺΞΧ ϯτͷ࡞ϩάΠϯ"QQ1BTTXPSEͷ࡞ϒϥβΛ։͍ͯIUUQT TUBHJOHCTLZBQQΛද͍ࣔͨ͠ w "OESPJE J04ͦΕͧΕͷQMBUGPSNͷॲཧ͕ඞཁ
@Composable fun App( onOpenBrowser: (String) -> Unit = {} )
{ RayleighTheme { … LoginScreen( viewModel = viewModel, onOpenBrowser = onOpenBrowser, ) } } shared/commonMain
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContent { MainView( onOpenBrowser = { startActivity( Intent(Intent.ACTION_VIEW, Uri.parse(it)) ) } ) } } @Composable fun MainView( onOpenBrowser: (String) -> Unit ) { App( onOpenBrowser = onOpenBrowser ) } androidApp/ shared/androidMain Android
struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController {
Main_iosKt.MainViewController( onOpenBrowser: { let url = URL(string:$0)! UIApplication.shared.open(url, options: [:], completionHandler: nil) } ) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } fun MainViewController( onOpenBrowser: (String) -> Unit ) = ComposeUIViewController { App( onOpenBrowser = onOpenBrowser ) } iosApp/iosApp/ContentView.swift shared/iosMain iOS
4UFQϩάΠϯ͢Δ w "1*Λୟ͍ͯฦ͖ͬͯͨUPLFOΛอଘ͠ͳ͍ͱ͍͚ͳ͍ w NVMUJQMBUGPSNTFUUJOHTΛ͏͜ͱʹ͢Δ w IUUQTHJUIVCDPNSVTTIXPMGNVMUJQMBUGPSNTFUUJOHT w ϓϥοτϑΥʔϜ͝ͱʹ4FUUJOHTͷੜΠϯελϯε͕ҟͳΔ w
%*͕ͳ͍ͱͭΒ͍
4UFQ%FQFOEFODZ*OKFDUJPO w "OESPJEͰEBHHFSIJMU͕σϑΝΫτ͕ͩ,.1Ͱ͑ͳ͍ w ,PJO w LPJODPSF,.1ʹରԠ͍ͯ͠Δ͕ w LPJODPNQPTF$PNQPTF.VMUJQMBUGPSNʹ·ͩରԠ͍ͯ͠ͳ͍ w
l$PVMEOPU fi OEJPJOTFSULPJOLPJODPNQPTFJPTBSNzͱౖΒΕ Δ w IUUQTJOTFSULPJOJPEPDTSFGFSFODFLPJODPNQPTFNVMUJQMBUGPSN 8*1
class AppComponent : KoinComponent { val authRepository: AuthRepository by inject()
} shared/commonMain @Composable fun App( appComponent: AppComponent, onOpenBrowser: (String) -> Unit = {} ) { RayleighTheme { val appViewModel = remember { AppViewModel(appComponent.authRepository) } … } }
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContent { MainView( appComponent = AppComponent(), onOpenBrowser = { startActivity( Intent(Intent.ACTION_VIEW, Uri.parse(it)) ) @Composable fun MainView( appComponent: AppComponent, onOpenBrowser: (String) -> Unit ) { App( appComponent = appComponent, onOpenBrowser = onOpenBrowser ) } androidApp/ shared/androidMain Android
struct ComposeView: UIViewControllerRepresentable { let appComponent = AppComponent() func makeUIViewController(context:
Context) -> UIViewController { Main_iosKt.MainViewController( appComponent: appComponent, onOpenBrowser: { let url = URL(string:$0)! UIApplication.shared.open(url, options: [:], completionHandler: nil) } ) } fun MainViewController( appComponent: AppComponent, onOpenBrowser: (String) -> Unit ) = ComposeUIViewController { App( appComponent = appComponent, onOpenBrowser = onOpenBrowser ) } iosApp/iosApp/ContentView.swift shared/iosMain iOS
4UFQλΠϜϥΠϯΛऔಘ͢Δ w ϩάΠϯ࣌ʹऔಘͨ͠5PLFOΛCFBSFSBVUIFOUJDBUJPOʹηοτ͢Δ w ,UPSͷ"VUIQMVHJO͏͚ͩ w IUUQTLUPSJPEPDTCFBSFSDMJFOUIUNM w UZQFͰΦϒδΣΫτ͕มΘΔ3FDPSEͱ͔$MBTTEJTDSJNJOBUPSGPS QPMZNPSQIJTN͏
w IUUQTHJUIVCDPN,PUMJOLPUMJOYTFSJBMJ[BUJPOCMPCNBTUFSEPDT KTPONEDMBTTEJTDSJNJOBUPSGPSQPMZNPSQIJTN
None
·ͱΊࠓޙ w /BWJHBUJPOͲ͏ͨ͠Β͍͍ͷͩʜ w %FDPNQPTF IUUQTHJUIVCDPNBSLJWBOPW%FDPNQPTF w %*͕ͭΒ͍ʜ
w LPJODPNQPTF͕ରԠͨ͠ΒָʹͳΔ͔ʁ w 5FYU'JFMEͷڍಈ͕J044JNVMBUPSͰ͓͔͍͠ʜ w $PNQPTFͰී௨ʹJ04ͷ6*͕Ͱ͖͍ͯ͢͝ͷͰɺ͍Ζ͍ΖΓ͍ͨ