回答編集履歴

2

回答の内容を編集

2025/03/29 13:38

投稿

utm.
utm.

スコア676

test CHANGED
@@ -2,13 +2,57 @@
2
2
  考え方としては
3
3
  xmlで記述していて見えない何かがインフレートやレンダリングを担っていたのを、コンポーザブル関数を使って明示的に行うと考えることですっきりしました。
4
4
  htmlやxmlのような別言語で書かれたプロパティではなくてオブジェクトとして一度定義しそれを使って関数に何かを任せるという発想で問題が解決できそうです。
5
+ やはりxmlのような別言語で書いていた部分がオブジェクトとなるのは若干違和感がありますが、そういうものだという認識で問題なさそうです。
6
+
7
+ そうすることで、
8
+ 1の問題は若干解決しました。
9
+ コンポーザブルな関数がhtmlの役割ではなく、クラスがhtmlの役割を担っている。
10
+
11
+ 2の問題についても、
5
- 若干違和感がありまが、classがxmlの代替で、コンポズ関数は今まで見えないころでしていたインフレートであるととらえると、フレームワークチックになりが、以下のようなコードが出来上がりました。
12
+ 関数にデータを渡だけよくって入力要素をパとしてえる必要がくな、見やさを担保できるようなりました。(time pickerなどの実装はまた一考必要かもしれない)
13
+
14
+ 3についても完全に解決したました。
15
+
6
- (和感も慣れ+フレムワークしてそいうものだと解釈すること特に引ところはなさそう)
16
+ 4については若干コンテキストがうが、下記のコドでは従来同じよな動作を担保できてい
17
+ また、stateについてはリストなどはまた違ってくるだろうが、その時その時で適切なstateを選ぶ方向でよいのではないかという結論に至りました。
7
18
 
8
19
  data classを用いて、
9
20
  TextFieldに必要な引数をすべて定義しておく
21
+
22
+
23
+ ベースとなるクラスでインスタンスを持つ。
24
+ また、関数ではベースとなるクラスを引数に取ることで、紐づけを行う
25
+ // 構造体 + 関数のやり方みたい。やっぱりkotlinはこういう結論に至るのか
26
+
27
+ 表示関連
28
+ (オブジェクトの型により表示状態を持つことで、LoginIdTextFieldなどのUI要素は不要になった)
29
+
30
+ LoginFormModelはインスタンスをdata classと保持することでログの出力や状態の比較が簡単になりそう(未検証)。
31
+ 考えをブラッシュアップしてソースコードに反映し以下ような構成になりました。
32
+ フレームワークとしての考えが提唱されていないのでModelだらけになっていますが。。。
33
+
34
+ textFieldのベース
10
- ```kt
35
+ ```kt
36
+ open class TextFieldModel {
37
+
38
+ var state: TextFieldState by mutableStateOf(TextFieldState(""))
39
+ protected set
40
+ open var action: TextFieldAction by mutableStateOf(
41
+ TextFieldAction(
42
+ onValueChange = ::updateInputValue)
43
+ )
44
+ protected set
45
+
46
+ open var style: TextFieldStyle by mutableStateOf(
47
+ TextFieldStyle()
48
+ )
49
+ protected set
50
+ fun updateInputValue(value: String) {
51
+ state = state.copy(inputValue = value)
52
+ }
53
+
54
+ }
11
- data class TextFieldParams(
55
+ data class TextFieldStyle(
12
56
  val modifier: Modifier = Modifier,
13
57
  val enabled: Boolean = true,
14
58
  val readOnly: Boolean = false,
@@ -31,126 +75,161 @@
31
75
 
32
76
  }
33
77
 
34
- ```
35
-
36
- ベースとなるクラスでインスタンスを持つ。
37
- また、関数ではベースとなるクラスを引数に取ることで、紐づけを行う
38
- // 構造体 + 関数のやり方みたい。やっぱりkotlinはこういう結論に至るのか
39
-
40
- ```kt
41
- open class InputStateBase {
78
+ data class TextFieldState(
42
-
43
- open protected var _inputValue = mutableStateOf("")
44
- val inputValue: State<String> = _inputValue
45
- open fun updateInputValue(value: String) {
46
- _inputValue.value = value
79
+ val inputValue: String ,
47
- }
48
-
49
- open var textFieldParams: TextFieldParams by mutableStateOf(
50
- TextFieldParams()
51
- )
80
+ )
52
- protected set
81
+ data class TextFieldAction(
53
-
82
+ val onValueChange: (String) -> Unit ,
54
- }
83
+ )
55
84
 
56
85
  @Composable
57
86
  fun AppTextFiled(
58
- state: InputStateBase
87
+ textFieldModel: TextFieldModel
59
88
  ) {
60
89
 
61
90
  TextField(
62
- value = state.inputValue.value,
63
- onValueChange = {state.updateInputValue(it)},
64
- modifier = state.textFieldParams.modifier,
65
- enabled = state.textFieldParams.enabled,
66
- readOnly = state.textFieldParams.readOnly,
67
- textStyle = state.textFieldParams.textStyle(),
68
- label = state.textFieldParams.label,
69
- placeholder = state.textFieldParams.placeholder,
70
- leadingIcon = state.textFieldParams.leadingIcon,
71
- trailingIcon = state.textFieldParams.trailingIcon,
72
- isError = state.textFieldParams.isError,
73
- visualTransformation = state.textFieldParams.visualTransformation,
74
- keyboardOptions = state.textFieldParams.keyboardOptions,
75
- keyboardActions = state.textFieldParams.keyboardActions,
76
- singleLine = state.textFieldParams.singleLine,
77
- maxLines = state.textFieldParams.maxLines,
78
- minLines = state.textFieldParams.minLines,
79
- interactionSource = state.textFieldParams.interactionSource,
80
- shape = state.textFieldParams.shape(),
81
- colors = state.textFieldParams.colors()
82
- )
83
- }
84
-
85
- ```
86
-
87
- こうすることで、
88
- 1の問題は若干解決しました。
89
- コンポーザブルな関数がhtmlの役割ではなく、クラスがhtmlの役割を担っている。
90
-
91
- 2の問題についても、
92
- 関数にデータを渡すだけでよくって、入力要素をパーツとして考える必要がなくなり、見やすさを担保できるようになった。(time pickerなどの実装はまた一考必要かもしれない)
93
-
94
- 3についても完全に解決した。
95
-
96
- 4については若干コンテキストが違う。またこの変更によりかくつきが発生するようになった気がするため少し調整が必要か。
97
-
98
-
99
- そのほかの構成要素の修正
100
- state関連
101
- ```kt
102
- // バリデーションが必要であれば継承とインタフェースを用いて拡張してください
103
- class LoginIdState : InputStateBase() {
104
-
105
- override var textFieldParams: TextFieldParams by mutableStateOf(
106
- TextFieldParams(
107
- label = { Text("Login ID") }
108
- )
109
- )
110
- }
111
-
112
- class LoginPasswordState : InputStateBase() {
113
-
114
- override var textFieldParams: TextFieldParams by mutableStateOf(
115
- TextFieldParams(
116
- label = { Text("Password") },
117
- visualTransformation = PasswordVisualTransformation(), // パスワードを隠す
118
- )
119
- )
120
- }
121
- ```
122
-
123
-
124
- 表示関連
125
- (オブジェクトの型により表示状態を持つことで、LoginIdTextFieldなどのUI要素は不要になった)
91
+ value = textFieldModel.state.inputValue,
92
+ onValueChange = {textFieldModel.action.onValueChange(it)},
93
+ modifier = textFieldModel.style.modifier,
94
+ enabled = textFieldModel.style.enabled,
95
+ readOnly = textFieldModel.style.readOnly,
96
+ textStyle = textFieldModel.style.textStyle(),
97
+ label = textFieldModel.style.label,
98
+ placeholder = textFieldModel.style.placeholder,
99
+ leadingIcon = textFieldModel.style.leadingIcon,
100
+ trailingIcon = textFieldModel.style.trailingIcon,
101
+ isError = textFieldModel.style.isError,
102
+ visualTransformation = textFieldModel.style.visualTransformation,
103
+ keyboardOptions = textFieldModel.style.keyboardOptions,
104
+ keyboardActions = textFieldModel.style.keyboardActions,
105
+ singleLine = textFieldModel.style.singleLine,
106
+ maxLines = textFieldModel.style.maxLines,
107
+ minLines = textFieldModel.style.minLines,
108
+ interactionSource = textFieldModel.style.interactionSource,
109
+ shape = textFieldModel.style.shape(),
110
+ colors = textFieldModel.style.colors()
111
+ )
112
+ }
113
+ ```
114
+
115
+ buttonのベース
116
+ ```kt
117
+ open class ButtonModel {
118
+
119
+ open var action: ButtonAction by mutableStateOf(
120
+ ButtonAction(
121
+ onClick = ::onClick)
122
+ )
123
+ protected set
124
+
125
+ open var style: ButtonStyle by mutableStateOf(
126
+ ButtonStyle()
127
+ )
128
+ protected set
129
+
130
+ open fun onClick() {
131
+ println("on click")
132
+ }
133
+
134
+ }
135
+ data class ButtonStyle(
136
+ val modifier: Modifier = Modifier,
137
+ val enabled: Boolean = true,
138
+ val interactionSource: MutableInteractionSource? = null,
139
+ val elevation: @Composable () -> ButtonElevation? = { ButtonDefaults.elevation() },
140
+ val shape: @Composable () -> Shape = { MaterialTheme.shapes.small },
141
+ val border: BorderStroke? = null,
142
+ val colors:@Composable () -> ButtonColors = { ButtonDefaults.buttonColors() },
143
+ val contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
144
+ )
145
+ data class ButtonAction(
146
+ val onClick: () -> Unit,
147
+ )
148
+ @Composable
149
+ fun AppButton(
150
+ buttonModel: ButtonModel,
151
+ content:@Composable RowScope.() -> Unit
152
+ ) {
153
+ Button(
154
+ onClick = buttonModel.action.onClick,
155
+ modifier = buttonModel.style.modifier,
156
+ enabled = buttonModel.style.enabled,
157
+ interactionSource = buttonModel.style.interactionSource,
158
+ elevation = buttonModel.style.elevation(),
159
+ shape = buttonModel.style.shape(),
160
+ border = buttonModel.style.border,
161
+ colors = buttonModel.style.colors(),
162
+ contentPadding = buttonModel.style.contentPadding,
163
+ content = content
164
+ )
165
+ }
166
+ ```
167
+
168
+ loginButtonModel
169
+ ```kt
170
+ class LoginButtonModel(
171
+ private val onDelegate: () -> Unit
172
+ ): ButtonModel() {
173
+ override var action: ButtonAction by mutableStateOf(
174
+ ButtonAction(
175
+ onClick = onDelegate)
176
+ )
177
+ protected set
178
+
179
+ }
180
+ ```
181
+
182
+ Loginに際するビジネスロジックの集約(これはサービスなのか?ドメインなのか?ViewModelなのか?アプリケーション的にはViewModelのスコープがないとスレッド的に問題ありそうだし、ドメインが状態を持っていいのかもわからない)
183
+ ```kt
184
+ class LoginFormModel(){
185
+ val loginIdModel = LoginIdStateModel()
186
+ val loginPasswordModel = LoginPasswordStateModel()
187
+
188
+ val loginButtonModel = LoginButtonModel(::submitLogin)
189
+
190
+ private fun submitLogin(){
191
+ // 入力をすべてリセット
192
+ clearForm()
193
+
194
+ }
195
+
196
+ private fun clearForm(){
197
+ loginIdModel.action.onValueChange("")
198
+ loginPasswordModel.action.onValueChange("")
199
+ }
200
+ }
201
+ ```
202
+ 当然インスタンスはviewModelが持つ
203
+ ```kt
204
+ class LoginViewModel () : ViewModel() {
205
+ val loginFormModel = LoginFormModel()
206
+ }
207
+ ```
208
+
209
+ UI
126
210
  ```kt
127
211
  @Composable
128
212
  fun LoginScreen(
129
213
  viewModel: LoginViewModel = viewModel<LoginViewModel>()
130
214
  ) {
131
215
 
132
- val screenState = viewModel.loginFormState
216
+ val loginFormModel = viewModel.loginFormModel
133
217
  Column(modifier = Modifier.padding(16.dp)) {
134
- AppTextFiled(screenState.loginIdState)
218
+ AppTextFiled(loginFormModel.loginIdModel)
135
- AppTextFiled(screenState.loginPasswordState)
219
+ AppTextFiled(loginFormModel.loginPasswordModel)
220
+
221
+ AppButton(loginFormModel.loginButtonModel){
222
+
136
- }
223
+ }
137
- }
224
+ }
138
-
225
+ }
226
+
227
+
139
- ```
228
+ ```
140
-
229
+
141
- ほかの構成変更
230
+ UI状態依存注入できるようにすべきのか疑問
142
-
143
-
144
- ---
145
- 追記
146
- そのほの構成要素変化しと書ましたが以下のようなクラはdata classと定義して状態比較や取得を簡単に出来るようにした方がいいかもなど思考していたのが抜ました
231
+ ログをだして、テストをけるようすべきら注入できたほういいが、そもそも予期できないような外部リソースの取得がのでセットする処理を淡々い気もする。。
147
- まだ、実験できていません。
148
-
149
- ```kt
150
- class Screen1State(){
151
- val loginIdState = LoginIdState()
232
+ TextFieldStateなどは処理ではなくて状態のラッパーでしかないので。。。
152
- val loginPasswordTextField = LoginPasswordState()
233
+ また、MVVMやMVCの観点からいってModelがアプリケーション内での状態を持つのはなんとなく不自然かもしれないので、用語を整理できればしたい。
153
- }
234
+
154
-
155
- ```
235
+ *回答を編集しました。文字数の関係から一部コードを削除しました。
156
-

1

考えを追記

2025/03/28 08:31

投稿

utm.
utm.

スコア676

test CHANGED
@@ -141,4 +141,16 @@
141
141
  そのほかの構成は変更なし
142
142
 
143
143
 
144
+ ---
145
+ 追記
146
+ そのほかの構成要素に変化なしと書きましたが以下のようなクラスはdata classと定義して状態の比較や取得を簡単に出来るようにした方がいいかも、などと思考していたのが抜けていました。
147
+ まだ、実験できていません。
144
148
 
149
+ ```kt
150
+ class Screen1State(){
151
+ val loginIdState = LoginIdState()
152
+ val loginPasswordTextField = LoginPasswordState()
153
+ }
154
+
155
+ ```
156
+