प्रस्तावना
एक बार जब मुझे विभिन्न प्रकारों के एक सूची दृश्य कार्ड में प्रदर्शित करने की आवश्यकता होती है, और यहां तक कि अलग-अलग एपीआई का उपयोग करके सर्वर से प्राप्त किया जाता है। जैसे, उपयोगकर्ता को आनन्दित होने दें और एक समाचार फ़ीड में देखें:
- वीडियो कार्ड, लाठी और विवरण के साथ;
- लेखकों या टैग के कार्ड, एक बड़े "सदस्यता" बटन के साथ।
जाहिर है, एक बड़े लेआउट के साथ छेड़छाड़ करना जिसमें कार्ड के लिए सभी कल्पनीय विकल्पों को ध्यान में रखना बुरा है, और यह इतना विस्तार करेगा।
दूसरी कठिनाई यह थी कि कार्ड के लिए डेटा स्रोत पूरी तरह से अलग-अलग सर्वर संसाधन हो सकते हैं, विभिन्न प्रकार के डेटा वापस करने वाले कई एपीआई के लिए एक साथ अनुरोधों का उपयोग करके सूची एकत्र की जानी थी।
खैर, ताकि जीवन शहद की तरह न लगे, सर्वर एपीआई को बदला नहीं जा सकता है।
API से लेकर ListView तक
Google I / O 2010 में Virgil Dobjanschi ने पूरी तरह से यह बताया कि REST API के साथ सहभागिता को कैसे लागू किया जाए। बहुत पहला पैटर्न पढ़ता है:
- गतिविधि ऐसी सेवा बनाती है जो REST API के लिए अनुरोध करती है;
- सेवा प्रतिक्रिया को पार्स करती है और कन्टैंटप्रॉइडर के माध्यम से डेटाबेस में डेटा को सहेजती है;
- गतिविधि डेटा परिवर्तन की सूचना प्राप्त करती है और दृश्य को अपडेट करती है।
UPD यहां सेवा का उपयोग करने के विषय पर एक छोटी सी होलिवर उठी है, इसलिए इस शब्द को "एक पुस्तकालय जो HTTP अनुरोधों को लागू करता है" के साथ प्रतिस्थापित करना बेहतर है - यह कोई रास्ता नहीं है।
इसलिए अंत में, सब कुछ काम करता है: हम एपीआई के लिए अनुरोधों का एक गुच्छा बनाते हैं, सामग्री का उपयोग करके डेटा को REST संसाधनों के प्रकारों से जुड़े अलग-अलग तालिकाओं में डालें, फ़ीड में नए डेटा की उपलब्धता के बारे में सूचित करें। लेकिन, हमेशा की तरह, दो समस्याएं हैं:
- कार्डों की सूची कैसे प्रदर्शित करें?
- टेप के लिए अनुरोध कैसे एकत्र करें?
हम विभिन्न प्रकार के कार्ड प्रदर्शित करते हैं
सबसे पहले, हम सरल के साथ सौदा करते हैं। समाधान आसानी से 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
कि क्या नए या पुराने विचारों का पुन: उपयोग करना है ... सामान्य तौर पर, सब कुछ बहुत अच्छा है, आपको बस कर्सर में
VIEW_TYPE_COLUMN
कॉलम प्राप्त करने की आवश्यकता है।
हम टेप के लिए SQL क्वेरी एकत्र करते हैं
निश्चितता के लिए, डेटाबेस में टेबल होने दें:
- वीडियो - फ़ीड के लिए वीडियो की एक सूची है।
कॉलम आईडी, शीर्षक, चित्र, अद्यतन। - लेखक, टैग - उन संस्थाओं की सूची में शामिल हैं जिनकी आप सदस्यता ले सकते हैं (एक से एक सर्वर एपीआई पर प्रदर्शित होते हैं)।
कॉलम आईडी, नाम, चित्र, अद्यतन।
कुल, आपको एक क्वेरी बनाने की आवश्यकता है जो निम्नलिखित कॉलम लौटाती है:
स्तंभ | वीडियो | लेखक | टैग | टिप्पणी |
---|---|---|---|---|
आईडी | video_id | author_id | tag_id | इसी तालिका में प्राथमिक कुंजी |
view_type | वीडियो | सदस्यता | सदस्यता | प्रदर्शित करने के लिए कार्ड का प्रकार |
content_type | वीडियो | लेखकों | टैग | सामग्री प्रकार - या तालिका का नाम, यदि यह अधिक सुविधाजनक है |
शीर्षक | video_title | शून्य | शून्य | वीडियो शीर्षक |
नाम | शून्य | AUTHOR_NAME | TAG_NAME | लेखक का नाम या टैग नाम |
चित्र | लिंक | लिंक | लिंक | तस्वीर के लिए लिंक |
अद्यतन | टाइमस्टैम्प | टाइमस्टैम्प | टाइमस्टैम्प | सर्वर अद्यतन समय |
मैं थोड़ा और समझाऊंगा।
- view_type - प्रदर्शन के प्रकार के लिए जिम्मेदार। कृपया ध्यान दें कि लेखकों और टैगों के लिए, प्रदर्शन प्रकार समान है।
- content_type - डेटा स्रोत के लिए जिम्मेदार। लेखक और टैग के लिए, यह पहले से ही अलग है, जो आपको आवश्यक होने पर अतिरिक्त डेटा के लिए वांछित तालिका या वांछित एपीआई को संदर्भित करने की अनुमति देता है।
- शीर्षक, नाम और चित्र - तालिका कॉलम जो प्रत्येक विशिष्ट तालिका के लिए सभी या विशिष्ट के लिए सामान्य हो सकते हैं
- अद्यतन - वह फ़ील्ड जिसके द्वारा पंक्तियों को परिणाम के रूप में क्रमबद्ध किया जाएगा।
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 में थोड़ी छोटी गाड़ी है, लेकिन ऐसी क्वेरी बनाने के लिए काम करने के तरीके।
इसलिए, गतिविधि हमारे ContentProvider से फ़ीड का अनुरोध कर रही है:
Cursor c = getContext().getContentResolver().query(Uri.parse("content://MyProvider/feed/"));
उसी समय,
MyProvider.query
विधि में,
MyProvider.query
निर्धारित करना आवश्यक है कि अनुरोध उरी टेप से किया गया है, और "बुद्धिमान" क्वेरी निर्माण मोड पर स्विच करें।
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 अनुरोध उत्पन्न करेगा जो हमें चाहिए और एडॉप्टर को आवश्यक डेटा देता है।
हमें सर्वर से डेटा अपडेट प्राप्त होता है
लेकिन यह क्या है? हम एपीआई वीडियो के दूसरे पृष्ठ का अनुरोध करते हैं, डेटा, लॉग्स को देखते हुए, डेटाबेस में संग्रहीत किया जाता है, लेकिन सूची दृश्य का उपयोग नहीं किया जाता है ...
बिंदु लोडर कॉलबैक का कार्यान्वयन है
@Override public Loader<Cursor> onCreateLoader(int loaderId, Bundle params) { return new CursorLoader( getContext(), Uri.parse("content://MyContentProvider/feed/"), ... ); }
जब कोई गतिविधि किसी ContentProvider का अनुरोध करती है, CursorLoader एक ContentObserver बनाता है जो Uri
content://MyProvider/feed/
लिए देखता
content://MyProvider/feed/
; जब हमारी सेवा सर्वर के एपीआई के लिए अनुरोध के परिणामों को सहेजती है, तो ContentProvider स्वचालित रूप से आपको किसी अन्य Uri के लिए डेटा परिवर्तन की सूचना देता है,
content://MyProvider/videos/
।
इस समस्या को कैसे सही ढंग से और अंत में हल करें, मुझे नहीं पता। मेरे आवेदन में, यह कोड में पर्याप्त था जो डेटाबेस में क्वेरी के परिणामों को फ़ीड में डेटा में परिवर्तन के बारे में स्पष्ट रूप से सूचित करने के लिए (विशिष्ट तालिका में परिवर्तन की सूचना प्रदाता पर पड़ता है) को संग्रहीत करता है:
getContext.getContentResolver().notifyChange(Uri.parse("content://MyProvider/feed/", null));
वैकल्पिक समाधान
- MergeCursor - कर्सर इंटरफ़ेस में कर्सर की एक सूची को लपेटता है, जब पुनरावृत्ति करता है, पहले कर्सर से सभी पंक्तियों को वापस करता है, फिर दूसरा, आदि।
मामले में जब अनुरोध में लाइनों का क्रम महत्वपूर्ण नहीं है - यह कोड को बहुत सरल बनाने की अनुमति देता है। - MatrixCursor - आप डेटाबेस तक पहुँचने के बिना किसी भी दो-आयामी सरणी में कर्सर इंटरफ़ेस प्रदान करने की अनुमति देता है। MergeCursor + छँटाई + MatrixCursor - मामले में एक लाभ देता है जब आपको सॉर्ट करने और बहुत बड़ी संख्या में पंक्तियों को दिखाने की आवश्यकता नहीं होती है।