回答編集履歴

5

微修正

2024/03/09 18:54

投稿

jimbe
jimbe

スコア12744

test CHANGED
@@ -41,7 +41,7 @@
41
41
  }
42
42
  }
43
43
  ```
44
- Page0Fragment.java
44
+ Page0Fragment.java (これを MainFragment の ViewPager page:0 に載せる)
45
45
  ```java
46
46
  import android.os.Bundle;
47
47
  import android.view.View;
@@ -99,7 +99,7 @@
99
99
  MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
100
100
 
101
101
  Button addButton = view.findViewById(R.id.add_button);
102
- addButton.setOnClickListener(v -> transitionToPass(view));
102
+ addButton.setOnClickListener(v -> transitionToPass());
103
103
 
104
104
  ListView budgetListView = view.findViewById(R.id.budget_listview);
105
105
  Adapter adapter = new Adapter();
@@ -108,8 +108,8 @@
108
108
  budgetListView.setAdapter(adapter);
109
109
  }
110
110
 
111
- private void transitionToPass(View view) {
111
+ private void transitionToPass() {
112
- @IdRes int containerViewId = ((View)view.getParent()).getId();
112
+ @IdRes int containerViewId = ((View)getView().getParent()).getId();
113
113
  getParentFragmentManager().beginTransaction()
114
114
  .replace(containerViewId, new PassFragment())
115
115
  .commit();
@@ -215,7 +215,7 @@
215
215
  MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
216
216
 
217
217
  Button cancelButton = view.findViewById(R.id.cancel_button);
218
- cancelButton.setOnClickListener(v -> transitionToReceive(view));
218
+ cancelButton.setOnClickListener(v -> transitionToReceive());
219
219
 
220
220
  Button saveButton = view.findViewById(R.id.save_button);
221
221
  saveButton.setEnabled(false);
@@ -235,12 +235,12 @@
235
235
 
236
236
  saveButton.setOnClickListener(v -> {
237
237
  viewModel.addBudgetName(budgetNameEdit.getText().toString());
238
- transitionToReceive(view);
238
+ transitionToReceive();
239
239
  });
240
240
  }
241
241
 
242
- private void transitionToReceive(View view) {
242
+ private void transitionToReceive() {
243
- @IdRes int containerViewId = ((View)view.getParent()).getId();
243
+ @IdRes int containerViewId = ((View)getView().getParent()).getId();
244
244
  getParentFragmentManager().beginTransaction()
245
245
  .replace(containerViewId, new ReceiveFragment())
246
246
  .commit();
@@ -253,8 +253,8 @@
253
253
  <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
254
254
  xmlns:app="http://schemas.android.com/apk/res-auto"
255
255
  android:layout_margin="8dp"
256
- android:layout_width="wrap_content"
256
+ android:layout_width="match_parent"
257
- android:layout_height="wrap_content">
257
+ android:layout_height="match_parent">
258
258
 
259
259
  <EditText
260
260
  android:id="@+id/budget_name_edit"
@@ -272,17 +272,13 @@
272
272
  android:layout_height="wrap_content"
273
273
  android:text="CANCEL"
274
274
  android:layout_marginEnd="10dp"
275
- android:layout_marginStart="50dp"
276
- app:layout_constraintBottom_toBottomOf="parent"
277
275
  app:layout_constraintEnd_toStartOf="@id/save_button"
278
- app:layout_constraintStart_toStartOf="parent"
279
276
  app:layout_constraintTop_toTopOf="@id/save_button" />
280
277
 
281
278
  <Button
282
279
  android:id="@+id/save_button"
283
280
  android:layout_width="wrap_content"
284
281
  android:layout_height="wrap_content"
285
- android:layout_marginTop="10dp"
286
282
  android:text="SAVE"
287
283
  app:layout_constraintBottom_toBottomOf="parent"
288
284
  app:layout_constraintEnd_toEndOf="parent"

4

修正抜け

2024/03/09 12:08

投稿

jimbe
jimbe

スコア12744

test CHANGED
@@ -191,7 +191,7 @@
191
191
 
192
192
  </androidx.constraintlayout.widget.ConstraintLayout>
193
193
  ```
194
- PassFragment.java (ダイアログ)
194
+ PassFragment.java
195
195
  ```java
196
196
  import android.os.Bundle;
197
197
 

3

Page0Fragment を追加、 PassFragment を ReceiveFragment と切り替える形に

2024/03/09 12:06

投稿

jimbe
jimbe

スコア12744

test CHANGED
@@ -19,7 +19,7 @@
19
19
  ---
20
20
  全体の構造が
21
21
  ![Activity内の各フラグメントとViewModelの関係](https://ddjkaamml8q8x.cloudfront.net/questions/2024-03-09/4fb59ba6-d1d8-4086-87be-e6fb95a83c6f.png)
22
- であれば、 PassFragment はダイアログにしたほうが簡単だと思います。
22
+ であれば、 ~~PassFragment はダイアログにしたほうが簡単だと~~ page:0 上で二つのフラグメントを切り替える FragmentContainerView を作るのが自然かと 思います。
23
23
 
24
24
  MainViewModel.java
25
25
  ```java
@@ -41,12 +41,44 @@
41
41
  }
42
42
  }
43
43
  ```
44
+ Page0Fragment.java
45
+ ```java
46
+ import android.os.Bundle;
47
+ import android.view.View;
48
+
49
+ import androidx.annotation.*;
50
+ import androidx.fragment.app.*;
51
+
52
+ public class Page0Fragment extends Fragment {
53
+ Page0Fragment() {
54
+ super(R.layout.fragment_page0);
55
+ }
56
+
57
+ @Override
58
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
59
+ super.onViewCreated(view, savedInstanceState);
60
+
61
+ if(savedInstanceState == null) {
62
+ getChildFragmentManager().beginTransaction()
63
+ .replace(R.id.fragment_container_view, new ReceiveFragment())
64
+ .commit();
65
+ }
66
+ }
67
+ }
68
+ ```
69
+ res/layout/fragment_page0.xml
70
+ ```xml
71
+ <?xml version="1.0" encoding="utf-8"?>
72
+ <androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
73
+ android:id="@+id/fragment_container_view"
74
+ android:layout_width="match_parent"
75
+ android:layout_height="match_parent" />
76
+ ```
44
77
  ReceiveFragment.java
45
78
  ```java
46
79
  import android.os.Bundle;
47
80
 
48
- import androidx.annotation.NonNull;
81
+ import androidx.annotation.*;
49
- import androidx.annotation.Nullable;
50
82
  import androidx.fragment.app.Fragment;
51
83
  import androidx.lifecycle.ViewModelProvider;
52
84
 
@@ -67,13 +99,20 @@
67
99
  MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
68
100
 
69
101
  Button addButton = view.findViewById(R.id.add_button);
70
- addButton.setOnClickListener(v -> new PassFragment().show(getChildFragmentManager(), null));
102
+ addButton.setOnClickListener(v -> transitionToPass(view));
71
103
 
72
104
  ListView budgetListView = view.findViewById(R.id.budget_listview);
73
105
  Adapter adapter = new Adapter();
74
106
  //MainViewModel の budgetNameListLiveData が設定 (setValue/postValue) されたら、その値をパラメータとして adapter.setList() を呼ぶように設定
75
107
  viewModel.getBudgetNameList().observe(getViewLifecycleOwner(), adapter::setList);
76
108
  budgetListView.setAdapter(adapter);
109
+ }
110
+
111
+ private void transitionToPass(View view) {
112
+ @IdRes int containerViewId = ((View)view.getParent()).getId();
113
+ getParentFragmentManager().beginTransaction()
114
+ .replace(containerViewId, new PassFragment())
115
+ .commit();
77
116
  }
78
117
 
79
118
  private static class Adapter extends BaseAdapter {
@@ -156,8 +195,7 @@
156
195
  ```java
157
196
  import android.os.Bundle;
158
197
 
159
- import androidx.annotation.NonNull;
198
+ import androidx.annotation.*;
160
- import androidx.annotation.Nullable;
161
199
  import androidx.fragment.app.*;
162
200
  import androidx.lifecycle.ViewModelProvider;
163
201
 
@@ -165,7 +203,7 @@
165
203
  import android.view.View;
166
204
  import android.widget.*;
167
205
 
168
- public class PassFragment extends DialogFragment {
206
+ public class PassFragment extends Fragment {
169
207
  public PassFragment() {
170
208
  super(R.layout.fragment_pass);
171
209
  }
@@ -177,7 +215,7 @@
177
215
  MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
178
216
 
179
217
  Button cancelButton = view.findViewById(R.id.cancel_button);
180
- cancelButton.setOnClickListener(v -> dismiss());
218
+ cancelButton.setOnClickListener(v -> transitionToReceive(view));
181
219
 
182
220
  Button saveButton = view.findViewById(R.id.save_button);
183
221
  saveButton.setEnabled(false);
@@ -197,8 +235,15 @@
197
235
 
198
236
  saveButton.setOnClickListener(v -> {
199
237
  viewModel.addBudgetName(budgetNameEdit.getText().toString());
200
- dismiss();
238
+ transitionToReceive(view);
201
239
  });
240
+ }
241
+
242
+ private void transitionToReceive(View view) {
243
+ @IdRes int containerViewId = ((View)view.getParent()).getId();
244
+ getParentFragmentManager().beginTransaction()
245
+ .replace(containerViewId, new ReceiveFragment())
246
+ .commit();
202
247
  }
203
248
  }
204
249
  ```

2

追加

2024/03/09 07:58

投稿

jimbe
jimbe

スコア12744

test CHANGED
@@ -15,3 +15,233 @@
15
15
  new ViewModelProvider(requireActivity()).get(MainViewModel.class);
16
16
  ```
17
17
  とすれば、同じアクティビティから同じモノを得られます。
18
+
19
+ ---
20
+ 全体の構造が
21
+ ![Activity内の各フラグメントとViewModelの関係](https://ddjkaamml8q8x.cloudfront.net/questions/2024-03-09/4fb59ba6-d1d8-4086-87be-e6fb95a83c6f.png)
22
+ であれば、 PassFragment はダイアログにしたほうが簡単だと思います。
23
+
24
+ MainViewModel.java
25
+ ```java
26
+ import androidx.annotation.NonNull;
27
+ import androidx.lifecycle.*;
28
+
29
+ import java.util.*;
30
+
31
+ public class MainViewModel extends ViewModel {
32
+ private MutableLiveData<List<String>> budgetNameListLiveData = new MutableLiveData<>(new ArrayList<>());
33
+ LiveData<List<String>> getBudgetNameList() {
34
+ //getValue したリストを書き換えられないように、 "Collections.unmodifiableList を返す LiveData" を返す
35
+ return Transformations.switchMap(budgetNameListLiveData, e -> new MutableLiveData<>(Collections.unmodifiableList(e)));
36
+ }
37
+ void addBudgetName(@NonNull String name) {
38
+ List<String> list = budgetNameListLiveData.getValue();
39
+ list.add(name);
40
+ budgetNameListLiveData.setValue(list);
41
+ }
42
+ }
43
+ ```
44
+ ReceiveFragment.java
45
+ ```java
46
+ import android.os.Bundle;
47
+
48
+ import androidx.annotation.NonNull;
49
+ import androidx.annotation.Nullable;
50
+ import androidx.fragment.app.Fragment;
51
+ import androidx.lifecycle.ViewModelProvider;
52
+
53
+ import android.view.*;
54
+ import android.widget.*;
55
+
56
+ import java.util.*;
57
+
58
+ public class ReceiveFragment extends Fragment {
59
+ public ReceiveFragment() {
60
+ super(R.layout.fragment_receive);
61
+ }
62
+
63
+ @Override
64
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
65
+ super.onViewCreated(view, savedInstanceState);
66
+
67
+ MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
68
+
69
+ Button addButton = view.findViewById(R.id.add_button);
70
+ addButton.setOnClickListener(v -> new PassFragment().show(getChildFragmentManager(), null));
71
+
72
+ ListView budgetListView = view.findViewById(R.id.budget_listview);
73
+ Adapter adapter = new Adapter();
74
+ //MainViewModel の budgetNameListLiveData が設定 (setValue/postValue) されたら、その値をパラメータとして adapter.setList() を呼ぶように設定
75
+ viewModel.getBudgetNameList().observe(getViewLifecycleOwner(), adapter::setList);
76
+ budgetListView.setAdapter(adapter);
77
+ }
78
+
79
+ private static class Adapter extends BaseAdapter {
80
+ private List<String> list = Collections.emptyList();
81
+
82
+ @SuppressWarnings("notifyDataSetChanged") //IDE での警告を抑制
83
+ void setList(List<String> list) {
84
+ this.list = new ArrayList(list); //防御コピー
85
+ notifyDataSetChanged();
86
+ }
87
+
88
+ @Override
89
+ public int getCount() {
90
+ return list.size();
91
+ }
92
+
93
+ @Override
94
+ public Object getItem(int position) {
95
+ return position;
96
+ }
97
+
98
+ @Override
99
+ public long getItemId(int position) {
100
+ return position;
101
+ }
102
+
103
+ @Override
104
+ public View getView(int position, View convertView, ViewGroup parent) {
105
+ return (convertView == null ? new ViewHolder(parent) : (ViewHolder)convertView.getTag())
106
+ .bind(list.get(position));
107
+ }
108
+
109
+ private class ViewHolder {
110
+ private final View itemView;
111
+ private final TextView text1;
112
+
113
+ ViewHolder(ViewGroup parent) {
114
+ itemView = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
115
+ itemView.setTag(this);
116
+ text1 = itemView.findViewById(android.R.id.text1);
117
+ }
118
+
119
+ View bind(String name) {
120
+ text1.setText(name);
121
+ return itemView;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ ```
127
+ res/layout/fragment_receive.xml
128
+ ```xml
129
+ <?xml version="1.0" encoding="utf-8"?>
130
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
131
+ xmlns:app="http://schemas.android.com/apk/res-auto"
132
+ android:layout_width="match_parent"
133
+ android:layout_height="match_parent">
134
+
135
+ <ListView
136
+ android:id="@+id/budget_listview"
137
+ android:layout_width="0dp"
138
+ android:layout_height="0dp"
139
+ app:layout_constraintBottom_toTopOf="@id/add_button"
140
+ app:layout_constraintEnd_toEndOf="parent"
141
+ app:layout_constraintStart_toStartOf="parent"
142
+ app:layout_constraintTop_toTopOf="parent" />
143
+
144
+ <Button
145
+ android:id="@+id/add_button"
146
+ android:layout_width="wrap_content"
147
+ android:layout_height="wrap_content"
148
+ android:text="ADD"
149
+ app:layout_constraintBottom_toBottomOf="parent"
150
+ app:layout_constraintEnd_toEndOf="parent"
151
+ app:layout_constraintStart_toStartOf="parent" />
152
+
153
+ </androidx.constraintlayout.widget.ConstraintLayout>
154
+ ```
155
+ PassFragment.java (ダイアログ)
156
+ ```java
157
+ import android.os.Bundle;
158
+
159
+ import androidx.annotation.NonNull;
160
+ import androidx.annotation.Nullable;
161
+ import androidx.fragment.app.*;
162
+ import androidx.lifecycle.ViewModelProvider;
163
+
164
+ import android.text.*;
165
+ import android.view.View;
166
+ import android.widget.*;
167
+
168
+ public class PassFragment extends DialogFragment {
169
+ public PassFragment() {
170
+ super(R.layout.fragment_pass);
171
+ }
172
+
173
+ @Override
174
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
175
+ super.onViewCreated(view, savedInstanceState);
176
+
177
+ MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
178
+
179
+ Button cancelButton = view.findViewById(R.id.cancel_button);
180
+ cancelButton.setOnClickListener(v -> dismiss());
181
+
182
+ Button saveButton = view.findViewById(R.id.save_button);
183
+ saveButton.setEnabled(false);
184
+
185
+ EditText budgetNameEdit = view.findViewById(R.id.budget_name_edit);
186
+ //名前の入力状態によって addButton を使えるか使えないかにする (これによって saveButton 押下時に有効/無効の判定が不要になる)
187
+ budgetNameEdit.addTextChangedListener(new TextWatcher() {
188
+ @Override
189
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { /*no process*/ }
190
+ @Override
191
+ public void onTextChanged(CharSequence s, int start, int before, int count) { /*no process*/ }
192
+ @Override
193
+ public void afterTextChanged(Editable s) {
194
+ saveButton.setEnabled(s.length() > 0);
195
+ }
196
+ });
197
+
198
+ saveButton.setOnClickListener(v -> {
199
+ viewModel.addBudgetName(budgetNameEdit.getText().toString());
200
+ dismiss();
201
+ });
202
+ }
203
+ }
204
+ ```
205
+ res/layout/fragment_pass.xml
206
+ ```xml
207
+ <?xml version="1.0" encoding="utf-8"?>
208
+ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
209
+ xmlns:app="http://schemas.android.com/apk/res-auto"
210
+ android:layout_margin="8dp"
211
+ android:layout_width="wrap_content"
212
+ android:layout_height="wrap_content">
213
+
214
+ <EditText
215
+ android:id="@+id/budget_name_edit"
216
+ android:layout_width="0dp"
217
+ android:layout_height="wrap_content"
218
+ android:hint="BudgetName"
219
+ app:layout_constraintBottom_toTopOf="@id/save_button"
220
+ app:layout_constraintEnd_toEndOf="parent"
221
+ app:layout_constraintStart_toStartOf="parent"
222
+ app:layout_constraintTop_toTopOf="parent" />
223
+
224
+ <Button
225
+ android:id="@+id/cancel_button"
226
+ android:layout_width="wrap_content"
227
+ android:layout_height="wrap_content"
228
+ android:text="CANCEL"
229
+ android:layout_marginEnd="10dp"
230
+ android:layout_marginStart="50dp"
231
+ app:layout_constraintBottom_toBottomOf="parent"
232
+ app:layout_constraintEnd_toStartOf="@id/save_button"
233
+ app:layout_constraintStart_toStartOf="parent"
234
+ app:layout_constraintTop_toTopOf="@id/save_button" />
235
+
236
+ <Button
237
+ android:id="@+id/save_button"
238
+ android:layout_width="wrap_content"
239
+ android:layout_height="wrap_content"
240
+ android:layout_marginTop="10dp"
241
+ android:text="SAVE"
242
+ app:layout_constraintBottom_toBottomOf="parent"
243
+ app:layout_constraintEnd_toEndOf="parent"
244
+ app:layout_constraintTop_toBottomOf="@id/budget_name_edit" />
245
+
246
+ </androidx.constraintlayout.widget.ConstraintLayout>
247
+ ```

1

図追加

2024/03/08 09:12

投稿

jimbe
jimbe

スコア12744

test CHANGED
@@ -8,6 +8,8 @@
8
8
  としており、 this つまり各々が管理している ViewModel を得ていますので、別々のモノを得ています。
9
9
  両方で同じモノを使いたいのであれば両方で同じ ViewModelStoreOwner を使わなければなりませんので、双方が乗っている(であろう)アクティビティを使います。
10
10
 
11
+ ![Activity/Fragment から ViewModel を得る](https://ddjkaamml8q8x.cloudfront.net/questions/2024-03-08/344fba56-6073-48b4-aa2f-7c13db30ef5b.png)
12
+
11
13
  (PassFragment はフィールドの初期化で ViewModel を得ていますがアクティビティはその時点では接続されていないので得られませんから ReceiveFragment のように onViewCreated 内等に移動させたうえで) 双方とも
12
14
  ```java
13
15
  new ViewModelProvider(requireActivity()).get(MainViewModel.class);