質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

MVVM

MVVM(Model View ViewModel)は構築上のデザインパターンで、表現ロジック(ViewModel)によってデータ(Model)からページ(View)を分離させます。

Xamarin

Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

Q&A

解決済

4回答

9043閲覧

【C#】【MVVM】非同期でObservableCollectionに追加したデータをViewに反映させる方法が知りたい

OXamarin

総合スコア59

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

MVVM

MVVM(Model View ViewModel)は構築上のデザインパターンで、表現ロジック(ViewModel)によってデータ(Model)からページ(View)を分離させます。

Xamarin

Xamarin(ザマリン)は、iPhoneなどのiOSやAndroidで動作し、C# 言語を用いてアプリを開発できるクロスプラットフォーム開発環境です。Xamarin Studioと C# 言語を用いて、 iOS と Android の両方の開発を行うことができます。

0グッド

0クリップ

投稿2017/07/09 11:01

###前提・実現したいこと
取得したツイートをViewにバインドしたい

###発生している問題
2点あります。

・1点目
ViewModelのコンストラクタ内で初期表示するツイートを取得しています。
Timelineプロパティの値をViewにバインディングさせています。
このプロパティはObservableCollectionなので、Addされたタイミングで変更通知がいっていると思いますが実際には何もバインドされません。

・2点目
コンストラクタの中で実行している Task.Run内の処理がawaitしているにも関わらずにすぐに処理が通り抜けた後に await内の処理が実行されています。なぜこのような動作になってしまうのでしょうか。

##Code (ViewModel)

C#

1public class MainPageViewModel : BindableBase 2{ 3 #region プロパティ 4 public ObservableCollection<string> TweetList { get; set; } 5 public ObservableCollection<long> UserListIds { get; set; } 6 public ObservableCollection<Timeline> Timeline { get; set; } 7 #endregion 8 9 #region フィールド 10 private Tweet _tweet = new Tweet(); 11 #endregion 12 13 #region コンストラクタ 14 public MainPageViewModel() 15 { 16 Task.Run(async () => 17 { 18 await _tweet.GetListIdsAsync("ユーザ名"); 19 UserListIds = _tweet.UserListIds; 20 await _tweet.GetListTimelineAsync(UserListIds.Skip(2).First()); 21 Timeline = _tweet.Timeline; 22 }); 23 } 24 #endregion 25}

##Code (View)

xaml

1<ListView ItemsSource="{Binding Timeline}" 2 RowHeight="80"> 3 <ListView.ItemTemplate> 4 <DataTemplate> 5 <ViewCell> 6 <Grid> 7 <Grid.RowDefinitions> 8 <RowDefinition Height="10" /> 9 <RowDefinition Height="20" /> 10 <RowDefinition Height="40" /> 11 </Grid.RowDefinitions> 12 <Grid.ColumnDefinitions> 13 <ColumnDefinition Width="50" /> 14 <ColumnDefinition Width="*" /> 15 <ColumnDefinition Width="50" /> 16 </Grid.ColumnDefinitions> 17 18 <!--トーク相手の画像--> 19 <Image Source="{Binding ProfileImage}" 20 Grid.RowSpan="3" 21 /> 22 <!--トーク相手の名前--> 23 <Label Grid.Row="1" Grid.Column="1" 24 Text="{Binding Name}" 25 FontAttributes="Bold" 26 /> 27 <!--最終トーク内容--> 28 <Label Grid.Row="2" Grid.Column="1" 29 Text="{Binding Text}" 30 FontSize="12" 31 /> 32 </Grid> 33 </ViewCell> 34 </DataTemplate> 35 </ListView.ItemTemplate> 36</ListView> 37

###試したこと
・Twitter APIからデータを取得するメソッドを同期的にして試してみましたが、対応していないのか例外で落ちてしまいます。
・コンストラクタ内でTimelineプロパティに対してAddをしてみると画面に反映されました。

###GitHub
全体のコードを以下のGitHubにアップロードしています。
https://github.com/karimatan1106/XamarinTweetForList

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

・1点目

cs

1Timeline = _tweet.Timeline;

これではTimeline のインスタンスを書き換えてしまっているので変更が通知されません。

cs

1Timeline.Add(_tweet.Timeline.Last());

こんな感じでAdd すれば変更が通知されると思います。

・2点目

cs

1 Task.Run(async () => 2 { 3 await _tweet.GetListIdsAsync("ユーザ名"); 4 UserListIds = _tweet.UserListIds; 5 await _tweet.GetListTimelineAsync(UserListIds.Skip(2).First()); 6 Timeline = _tweet.Timeline; 7 });

Task.Run をawait していないからでは?
Task.Run 内部のデリゲートだけでいいと思います。
ただ、非同期処理をコンストラクタで実行するよりは、何らかのトリガー(ボタンをタップするなど)を持って実行すべきかと思います。

なので、コンパイルはしていないですが以下のようになるのではないでしょうか。

cs

1await _tweet.GetListIdsAsync("ユーザ名"); 2UserListIds.Add(_tweet.UserListIds.Last()); 3await _tweet.GetListTimelineAsync(UserListIds.Skip(2).First()); 4Timeline.Add(_tweet.Timeline.Last());

投稿2017/07/09 15:11

IYEMON018

総合スコア202

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

まずUserListIdsやTimelineはその書き方ですと実体が定義されてないでnullになっていると思いますがそれは意図通りでしょうか?
Task.Runの中のTimeline=_tweet.Timelineで_tweet.TimelineがさしているインスタンスがMainPageViewModelのTimelineにもセットされますがこれも意図通りでしょうか?
本来やりたいことはMainPageViewModel内でTimelineとして扱われるObservableCollectionを生成し、そこに取得されたtweetの中身をコピーすることなのではと思いますが、そうであるならばこのコードでは上記のように不適切です。

ほかの方も言われているようにコンストラクタ内でそのようにTask.Runしても処理実行の終了を待機はしません。したい場合はこちらの質問と同様にすればいいと思いますが、本来やるべきでない実装です。
コンストラクタでの非同期メソッドの実行
またTask.Run内でそのままObservableCollectionに対し編集を行った場合、UIスレッド以外からの編集になりますのでViewがバインディングしている場合は落ちます。間にBeginInvokeOnMainThreadを挟む必要があります。

またそのコードではTimelineに別のObservableCollectionを代入していますが、それではView側はそれを検知はしません。INotifyPropertyChangedのメソッドを使ってTimelineが変わったとNotifyする必要があります。ただしおそらくこのコレクションの入れ替えは上に書いたように本来の意図とは違うのではないかと思いますが。

どこでMainPageViewModelをMainPageのBindingContextにセットされているのかわかりませんが、おそらくTask.Runの実行が終わる前にBindingContextにセットされる->TimelineはまだnullなのでBindingされない->Task.Runの実行が終わってTimelineにセットされたがMainPage側はそれを知る由もない、となっているのではとエスパーします。

投稿2017/07/10 06:01

omanuke

総合スコア109

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

1点目

ViewにBindしているのはMainPageViewModel のTimelineなのに、取得した情報をAddしているのは_tweetインスタンスの Timelineだからでは? Timeline = _tweet.Timeline を有効にしたいのであれば、

C#

1//MainPageViewModel 2ObservableCollection<Timeline> _timeline; 3public ObservableCollection<Timeline> Timeline 4{ 5 get{ return _timeline;} 6 set 7 { 8 if( _timeline == value ) 9 return; 10 _timeline = value; 11 OnPropertyChanged(); 12 } 13}

のようにObservableCollection自体が差し替えられたことを通知してあげる必要があるかと。

2点目

GetListIdsAsync内で何をやっているか追えていないですが、

C#

1await Task.Run(async () => 2{ 3 await task1(); 4 await task2(); 5});

じゃないでしょうか? Task.Run内でのawait って私はやらないパターンですが。。

投稿2017/07/10 00:44

ebiryo

総合スコア797

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

下記記事のように、非同期処理内で配列にアクセスする処理を、Device.BeginInvokeOnMainThreadに渡して実施してみてはいかがでしょうか。

http://running-cs.hatenablog.com/entry/2016/07/26/220732

投稿2017/07/09 13:28

runcs

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問