さまざまな種類のアイテムとさまざまなデータプロバイダーのリスト

まえがき


一度、異なるタイプの1つのListViewカードに表示する必要があり、さらに異なるAPIを使用してサーバーから受け取りました。 同様に、ユーザーに喜ばせて、1つのニュースフィードで次の情報を見てみましょう。

明らかに、考えられるすべてのカードのオプションを考慮に入れる1つの大きなレイアウトをいじるのは悪いことであり、まあまあ拡大します。



2番目の難点は、カードのデータソースが完全に異なるサーバーリソースになる可能性があり、異なるタイプのデータを返す複数の異なるAPIへの同時リクエストを使用してリストを収集する必要があったことです。



まあ、人生が蜜のように見えないように、サーバーAPIは変更できません。

APIからListViewへ


Google I / O 2010の Virgil Dobjanschiは、REST APIとの対話を実装する方法を完全に説明しました。 最初のパターンは次のとおりです。
  1. アクティビティは、REST APIへのリクエストを行うサービスを作成します。
  2. サービスは応答を解析し、ContentProviderを介してデータベースにデータを保存します。
  3. アクティビティはデータ変更の通知を受け取り、ビューを更新します。


UPDここでは、サービスの使用というトピックに関する小さなホリバーが発生したため、この言葉を「HTTPリクエストを実装するライブラリ」に置き換えた方がよいでしょう。どちらの方法でもかまいません。

したがって、最終的にはすべてが機能します:APIに対して大量のリクエストを作成し、ContentProviderを使用してRESTリソースのタイプに関連付けられた別のテーブルにデータを挿入し、notifyChangeを使用してフィード内の新しいデータの可用性を通知します。 しかし、いつものように、2つの問題があります。



さまざまな種類のカードを表示します


最初に、より単純なものに対処しましょう。 ソリューションはGoogleで簡単に見つけられるので、簡単に説明します。
カードリストアダプターでは 、メソッド再定義します。

@Override int getViewTypeCount() { //   ,       return VIEW_TYPE_COUNT; } @Override int getItemViewType(int position) { //          Cursor c = (Cursor)getItem(position); int columnIndex = c.getColumnIndex(VIEW_TYPE_COLUMN); return c.getInt(columnIndex); } @Override void bindView(View view, Context context, Cursor c) { //           int columnIndex = c.getColumnIndex(VIEW_TYPE_COLUMN); int viewType = c.getInt(columnIndex); switch(viewType) { case VIEW_TYPE_VIDEO: bindVideoView(view); break; case VIEW_TYPE_SUBSCRIPTION: //    } } @Override View newView(Context context, Cursor cursor, ViewGroup parent) { //        int columnIndex = c.getColumnIndex(VIEW_TYPE_COLUMN); int viewType = c.getInt(columnIndex); switch(viewType) { case VIEW_TYPE_VIDEO: return newVideoView(cursor); case VIEW_TYPE_SUBSCRIPTION: //    } } 


さらに、すばらしいCursorAdapterクラスはそれ自体ですべてを実行します。異なるタイプのビューの個別のビューキャッシュを初期化し、新しいビューを作成するか、古いビューを再利用するかを判断します...一般に、すべてが素晴らしいです。カーソルでVIEW_TYPE_COLUMN列を取得するだけです。

テープのSQLクエリを収集します


明確にするために、データベースにテーブルがあります:



合計、次の列を返すクエリを作成する必要があります。

コラムビデオ著者タグ付け解説
idvideo_idauthor_idtag_id対応するテーブルの主キー
view_typeビデオサブスクリプションサブスクリプション表示するカードの種類
content_type動画著者タグコンテンツタイプ-またはテーブル名(より便利な場合)
タイトルvideo_titleヌルヌルビデオタイトル
お名前ヌルauthor_nametag_name著者名またはタグ名
リンクリンクリンク写真へのリンク
更新しましたタイムスタンプタイムスタンプタイムスタンプサーバー更新時間


もう少し説明します。


sqliteでは、クエリは非常に簡単です。

 SELECT 0 as view_type, 'videos' as content_type, title, NULL as name, picture, updated FROM videos UNION ALL SELECT 1 as view_type, 'authors' as content_type, NULL as title, name, picture, updated FROM authors UNION ALL SELECT 1 as view_type, 'tags' as content_type, NULL as title, name, picture, updated FROM tags ORDER BY updated 


もちろん、このようなクエリを「手作業で」作成することもできますが、 SQLiteQueryBuilderには少しバグがありますが、そのようなクエリを作成するための作業メソッドがあります。

そのため、ActivityはContentProviderからフィードを要求しています:

 Cursor c = getContext().getContentResolver().query(Uri.parse("content://MyProvider/feed/")); 


同時に、 MyProvider.queryメソッドMyProvider.query 、要求がUriテープに対して行われたことを判断し、「インテリジェント」クエリ構築モードに切り替える必要があります。

 Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { if (isFeedUri(contentUri)) return buildFeedUri(); //       // ... } Cursor buildFeedUri() { //   "-"      HashSet<String> unionColumnsSet = new HashSet<String>(); //  Uri  ,    (videos, authors  tags) List<Uri>contentUriList = getSubqueryContentUriList(); //       viewType String[] viewTypeColumns = new String[contentUriList.size()]; //      contentType String[] contentTypeColumns = new String[contentUriList.size()]; for (int i=0; i<contentUriList.size(); i++) { Uri contentUri = contentUriList.get(i); //       viewTypeColumns[i] = getViewTypeExpr(contentUri); // "0 as view_type" //   content_type contentTypeColumns[i] = getContentTypeExpr(contentUri); // "'videos' as content_type" //      List<String> projection = getProjection(contentUri); //       unionColumnsSet.addAll(projection); } // ,       ,  :  , //  content-type    ,    . String[] subqueries = new String[contentUriList.size()]; for (int i=0; i<contentUriList.size(); i++) { Uri contentUri = contentUriList.get(i); SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); builder.setTables(getTable(contentUri)); //         "1 as content_type" //     ,  builder   //  "SELECT X as Y"   String[] unionColumns = prependContentTypeExpr(contentTypeColumns[i], unionColumnSet); //    ""     "0 as view_type" //  ,       Set<String> projection = prependViewTypeExpr(viewTypeColumns[i], getProjection(contentUri)); //  ,   String selection = computeWhere(contentUri); subqueries[i] = builder.buildUnionSubQuery( "content_type", // typeDiscriminatorColumn -   , //        unionColumns, projection, 0, getTable(contentUri), //    content_type // (      ) selection, null, // selectionArgs -   buildUnionSubQuery    // (   API level 1,  API level 11 -   ) null, // groupBy null // having ); } //   ,        . SQLiteQueryBuilder builder = new SQLiteQueryBuilder() String orderBy = "updated DESC"; String query = builder.buildUnionQuery( subqueries, orderBy, null // limit -   ,  . ); return getDBHelper().getReadableDatabase().rawQuery( query, null // selectionArgs -    ); } 


一般に、例が正しく記述されている場合、 content://MyProvider/feed/にアクセスするときにcontent://MyProvider/feed/ ContentProviderは必要なUNION要求を生成し、必要なデータをアダプターに提供します。

サーバーからデータの更新を受け取ります



しかし、それは何ですか? APIビデオの2ページ目をリクエストします。ログから判断して、データはデータベースに保存されますが、ListViewは更新されません...
ポイントはLoaderCallbacksの実装です

 @Override public Loader<Cursor> onCreateLoader(int loaderId, Bundle params) { return new CursorLoader( getContext(), Uri.parse("content://MyContentProvider/feed/"), ... ); } 


アクティビティがContentProviderを要求すると、CursorLoaderはUri content://MyProvider/feed/を監視するContentObserverを作成しますcontent://MyProvider/feed/ ; サービスがリクエストの結果をサーバーのAPIに保存すると、ContentProviderは別のUri、 content://MyProvider/videos/データ変更を自動的に通知しますcontent://MyProvider/videos/

この問題を正しく最終的に解決する方法はわかりません。 私のアプリケーションでは、クエリの結果をデータベースに保存するコードでは、フィードのデータの変更について明示的に通知するのに十分でした(特定のテーブルの変更の通知はプロバイダーに当てはまります)。

 getContext.getContentResolver().notifyChange(Uri.parse("content://MyProvider/feed/", null)); 


代替ソリューション


Source: https://habr.com/ru/post/J221851/


All Articles