Oct 30, 2014

If you want to integrate Lucene with your android application, this post will get you started.  Lucene provides you with a wide range of searching options  like  Fuzzy Search, wildcard search, etc.  So, you can use this in your android application, if you want to provide search option over your custom data model.

In the code shown below searches will be near real time as I am passing IndexWriter instance to it, so IndexReader will be created using the passed IndexWriter instance.  Also, as creation of IndexWriter and SearcherManager is expensive, so the best place to initialize them is in the application class.

Initialization: The application class which initializes the IndexWriter and SearcherManager.

public class FeedReaderApplication extends Application {

public static final SearcherManager getSearcherManager(){return searcherManager;}
public static final IndexWriter getIndexWriter(){return indexWriter;}
private static SearcherManager searcherManager=null;
private static IndexWriter indexWriter=null;

@Override
public void onCreate() {
//pick the properties from user preferences
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
Analyzer analyzer= new SimpleAnalyzer(Version.LUCENE_41);

IndexWriterConfig config=new IndexWriterConfig(Version.LUCENE_41,analyzer);
//pick the buffer size from property
String memorySize=preferences.getString("lucene_memory_size","5.0");
config.setRAMBufferSizeMB(Double.valueOf(memorySize));
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
//create index on external directory under lucene folder
File path= new File(getApplicationContext().getExternalFilesDir(null),"lucene");
try {
Directory directory= FSDirectory.open(path);
indexWriter=new IndexWriter(directory,config);
boolean applyAllDeletes = true;
//no need to warm the search
searcherManager = new SearcherManager(indexWriter, applyAllDeletes, null);
} catch (IOException e) {
Log.e(tag,"Error occurred while opening indexWriter/SearcherManager"+ e.getMessage(),e);
}
}

}


Now, In the example application I am using Sqlite database to store the Feed data, but titles are being analyzed and stored in the lucene index. Also, I am using the SimpleAnalyzer rather than standard analyzer as the StandardAnalyzer does stop word filtering before storing the terms which is not going to work out for us as the user might search with stop words and find no matches.



public class LuceneSearchUtil {
private static final String tag = LuceneSearchUtil.class.getName();

public LuceneSearchUtil() {
}
//insert articles id,title and feedid
public static void insertArticleDocument(ContentValues contentValues) {
try {
IndexWriter writer = FeedReaderApplication.getIndexWriter();
Document document = new Document();
//don't analyze id field, store as such
Field idField = new StringField(FeedSQLLiteHelper.COLUMN_ID, String.valueOf(contentValues.get(FeedSQLLiteHelper.COLUMN_ID)), Field.Store.YES);
document.add(idField);
//analyze the url field so textfield
Field titleField = new TextField(FeedSQLLiteHelper.COLUMN_ARTICLE_TITLE, String.valueOf(contentValues.get(FeedSQLLiteHelper.COLUMN_ARTICLE_TITLE)), Field.Store.YES);
document.add(titleField);
Field feedId= new StringField(FeedSQLLiteHelper.COLUMN_ARTICLE_FEED_ID,String.valueOf(contentValues.get(FeedSQLLiteHelper.COLUMN_ARTICLE_FEED_ID)), Field.Store.YES);
document.add(feedId);
writer.addDocument(document);
} catch (IOException e) {
Log.e(tag, "Unable to add document as " + e.getMessage(), e);
}
}

//searching the articles searchterm is passed and broken down into individual terms
public static ArrayList<String> searchAndGetMatchingIds(String searchTerm) {
ArrayList result=new ArrayList<String>();
//get the searchermanager
SearcherManager searcherManager = FeedReaderApplication.getSearcherManager();
IndexSearcher indexSearcher = null;

indexSearcher = searcherManager.acquire();
//split on space
String[] terms= searchTerm.split("[\\s]+");
//multiple terms are to be searched
SpanQuery[] spanQueryArticleTitle=new SpanQuery[terms.length];
int i=0;
for (String term:terms){
//wildcardquery
WildcardQuery wildcardQuery=new WildcardQuery(new Term(FeedSQLLiteHelper.COLUMN_ARTICLE_TITLE,term.toLowerCase()));
spanQueryArticleTitle[i]=new SpanMultiTermQueryWrapper<WildcardQuery>(wildcardQuery);
i=i+1;
}
//no words between the typed text you could increase this but then performance will be lowered
SpanNearQuery spanNearQuery1=new SpanNearQuery(spanQueryArticleTitle,0,true);
TopDocs topDocs=null;
try {
//execute topN query
topDocs = indexSearcher.search(spanNearQuery1, ProjectConstants.LUCENE_TOP_N);
if(topDocs!=null){
for(ScoreDoc scoreDoc:topDocs.scoreDocs){
Document document= indexSearcher.doc(scoreDoc.doc);
String id= document.get(FeedSQLLiteHelper.COLUMN_ID);
result.add(id);
}
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
searcherManager.release(indexSearcher);
} catch (IOException e) {
Log.e(tag,"Exception while releasing Index Searcher "+e.getMessage(),e);
}
}

return result;
}

//sample delete method

public static void deleteArticlesByFeedId(String feedId){
IndexWriter indexWriter = FeedReaderApplication.getIndexWriter();
TermQuery query=new TermQuery(new Term(FeedSQLLiteHelper.COLUMN_ARTICLE_FEED_ID,feedId));
try {
indexWriter.deleteDocuments(query);
} catch (IOException e) {
Log.e(tag, "Unable to delete document as " + e.getMessage(), e);
}
try {
indexWriter.commit();
} catch (IOException e) {
Log.e(tag, "Unable to commit changes " + e.getMessage(), e);
}
}
}


Note the search method in the above code. It is going to split the query that user passed into individual terms and then search each of those terms by using SpanNearQuery with a word distance of 0, which means that whatever user has typed must be matched without a word gap. For example if user types:  “Sweet Orange ” then those two terms will be matched only if there is no word between them in the article title.  Also note that Lucene returns top matching results, so when you pass these id’s to your database for retrieving the actual data from the database, you must make sure that the returned data is in that order.  Here is the relevant snippet from the AsyncTaskLoader.



 @Override
public List<FeedArticleSummary> loadInBackground() {
//query the searchterm
ArrayList<String> ids= LuceneSearchUtil.searchAndGetMatchingIds(searchTerm);
ArrayList results=new ArrayList();
//returns all the articles that match
HashMap<String,FeedArticleSummary> map= fdb.getEntriesForFeedByIds(ids);
//order them
if(map!=null){
for(String id:ids){
if(map.get(id)!=null){
results.add(map.get(id));
}

}
}

return results;
}


Now, all you need to do is invoke the loader to query and load the data when the user uses the SearchView in your application.  Here are the implemented methods for the SearchView.OnQueryTextListener.



 @Override
public boolean onQueryTextSubmit(String queryText) {
//let's set a threshold
if (queryText!=null&&queryText.trim().length() > 5) {
Loader loader = getActivity().getSupportLoaderManager().getLoader(ProjectConstants.LOADER_ARTICLES_SEARCH);
if (loader != null && !loader.isReset()) {
getActivity().getSupportLoaderManager().restartLoader(ProjectConstants.LOADER_ARTICLES_SEARCH, args, _self);
} else {
getActivity().getSupportLoaderManager().initLoader(ProjectConstants.LOADER_ARTICLES_SEARCH, args, _self);
}
if(!pd.isShowing()){
//show the progressdialog
pd.show();
}
}
return true;
}
@Override
public boolean onQueryTextChange(String s) {
//not handling this
return false;
}




Now in onloadfinished method just replace the data in your arrayadapter and you are set. That's it now you have integrated lucene into your application. 



device-search

Posted on Thursday, October 30, 2014 by Unknown

Oct 5, 2014

android_search_view 

In the last post, I had explained how you can use Retrofit API to consume Feedly’s feed search API. In this post, I will cover the integration of a SearchView with the feed search API. 

To integrate SearchView with the custom data source, you need to implement the following  interfaces  in the fragment or activity.

  • SearchView.OnQueryTextListener: This handles firing a query with the data provider as the user starts typing in the SearchView.
  • SearchView.OnSuggestionListener: This handles click or selection of the suggested result from search.

The code for the implemented methods of these interfaces is show below:-

//for onQueryTextListener
@Override
public boolean onQueryTextSubmit(String s) {
if (s.length() > 2) {
loadData(s);
}
return true;
}

@Override
public boolean onQueryTextChange(String s) {
if (s.length() > 2) {
loadData(s);
}
return true;
}

//for OnSuggestionListener

@Override
public boolean onSuggestionSelect(int position) {
Cursor cursor = (Cursor) searchView.getSuggestionsAdapter().getItem(position);
String feedName = cursor.getString(4);
searchView.setQuery(feedName, false);
searchView.clearFocus();
return true;
}

@Override
public boolean onSuggestionClick(int position) {
Cursor cursor = (Cursor) searchView.getSuggestionsAdapter().getItem(position);
String feedName = cursor.getString(4);
searchView.setQuery(feedName, false);
searchView.clearFocus();
return true;
}


 



The layout for the  customized list item which will appear when the user starts typing in the SearchView is shown below:-



<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/icon_feed"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:layout_alignParentStart="true"
android:contentDescription="@string/ImgContentDescription"
/>
<TextView
android:id="@+id/feed_url_text"
android:layout_width="wrap_content"
android:layout_height="23dp"
android:paddingLeft="5dp"
android:layout_toRightOf="@id/icon_feed"
android:paddingTop="0dp"
android:paddingBottom="2dp"
/>
<LinearLayout
android:id="@+id/subscriber_layout"
android:orientation="horizontal"
android:layout_below="@+id/feed_url_text"
android:layout_toRightOf="@id/icon_feed"
android:layout_width="wrap_content"
android:layout_height="25dp">
<ImageView
android:src="@drawable/icon_user_light"
android:paddingLeft="5dp"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/subscriber_count"
android:text="@string/feed_name"
android:layout_width="wrap_content"
android:paddingTop="2dp"
android:paddingLeft="2dp"
android:layout_height="23dp" />
</LinearLayout>

</RelativeLayout>


As, I am using a custom layout for the list item that appears in the search, So I have extended the SimpleCursorAdapter to  bind the view to the underlying data as shown below.



public class SearchFeedResultsAdaptor extends SimpleCursorAdapter {
private static final String tag=SearchFeedResultsAdaptor.class.getName();
private Context context=null;
public SearchFeedResultsAdaptor(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
super(context, layout, c, from, to, flags);
this.context=context;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
ImageView imageView=(ImageView)view.findViewById(R.id.icon_feed);
TextView textView=(TextView)view.findViewById(R.id.feed_url_text);
TextView subscribersView=(TextView)view.findViewById(R.id.subscriber_count);
ImageTagFactory imageTagFactory = ImageTagFactory.newInstance(context, R.drawable.rss_icon);
imageTagFactory.setErrorImageId(R.drawable.rss_icon);
ImageTag tag = imageTagFactory.build(cursor.getString(2),context);
imageView.setTag(tag);
FeedReaderApplication.getImageManager().getLoader().load(imageView);
textView.setText(cursor.getString(4) + " : " + cursor.getString(1));
subscribersView.setText(cursor.getString(3));


}
}


In this adapter class, I am binding the layout components to the elements returned by cursor in the overridden bindView method . I am also using the novoda image loader to load the icon image from the URL returned by Feedly service.



Since, I am using a fragment , so the code to initialize the SearchView  is placed in the  OnActivityCreated method as shown below.



 @Override
//list of columns
public static String[] columns = new String[]{"_id", "FEED_URL", "FEED_ICON", "FEED_SUBSCRIBERS", "FEED_TITLE"};

public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
searchView = (SearchView) getView().findViewById(R.id.searchFeedView);
searchView.setOnQueryTextListener(this);
searchView.setOnSuggestionListener(this);
mSearchViewAdapter = new SearchFeedResultsAdaptor(this.getActivity(), R.layout.search_feed_list_item, null, columns,null, -1000);
searchView.setSuggestionsAdapter(mSearchViewAdapter);
}


The loadData method which is invoked from the onQueryTextChanged  method loads the Feedly search results as shown below:-



 private void loadData(String searchText) {
//specify endpoint and build the restadapter instance
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://feedly.com")
.build();
//Now use restadapter to create an instance of your interface
FeedlySuggestionsService searchService = restAdapter.create(FeedlySuggestionsService.class);
//populate the request parameters
HashMap queryMap = new HashMap();
queryMap.put("query", searchText);
//implement the Callback method for retrieving the response
searchService.searchFeeds(queryMap, new Callback<FeedlyResponse>() {
@Override
public void success(FeedlyResponse feedlyResponse, Response response) {
MatrixCursor matrixCursor = convertToCursor(feedlyResponse.getResults());
mSearchViewAdapter.changeCursor(matrixCursor);
}

@Override
public void failure(RetrofitError error) {
Log.e(tag, error.toString());
}
});
}


If you notice the highlighted lines in the above method, you will see that the result returned by the REST service is in List format. Now, as the SearchView accepts only CursorAdapter as its SuggestionAdapter, so we have to convert the List into a Cursor object.  To do so, I have created a  convertToCursor method which iterates the List to return a MatrixCursor object.  After the conversion is done, I am just replacing the existing cursor of the Suggestion Adapter with this new value.  The method to convert the list to MatrixCursor is shown below:-



 private MatrixCursor convertToCursor(List<FeedlyResult> feedlyResults) {
MatrixCursor cursor = new MatrixCursor(columns);
int i = 0;
for (FeedlyResult feedlyResult : feedlyResults) {
String[] temp = new String[5];
i = i + 1;
temp[0] = Integer.toString(i);

String feedUrl = feedlyResult.getFeedId();
if (feedUrl != null) {
int index = feedUrl.indexOf("feed/");
if (index != -1) {
feedUrl = feedUrl.substring(5);
}
}
temp[1] = feedUrl;
temp[2] = feedlyResult.getIconUrl();
temp[3] = feedlyResult.getSubscribers();
temp[4] = feedlyResult.getTitle();
cursor.addRow(temp);
}
return cursor;
}


The above method takes the column names for the Cursor from a static String array as shown above the onActivityCreated  method.



That’s it ! now you have a SearchView  integrated with a REST service.

Posted on Sunday, October 05, 2014 by Unknown

Recently, I noticed a poor implementation of dynamic query in our application environment.  As the query was complex and its where clauses were generated dynamically, the developer chose not to implement the bind variables.  Now, some of you will be aware of the disadvantages of not using bind variables, but i have mentioned them below just to recap:-

  • It is a security risk as it allows SQL injection attack
  • It causes a major bottleneck on database as every time a search is submitted, the query will need to be parsed and for this the database acquires a latch.  A latch is like a synchronization code, so at a time only a single query might acquire the latch. thus, if you are parsing the query regularly, you are slowing down your database unnecessarily. 
  • Every time the query is parsed for different values a new statement will be created and stored in shared SQL area, so you have added memory problems to the list of issues.

 

Now that we have seen the issues, the solution then is to use the bind variables.

The problem:  Search was complex and developer did not know how many different clauses will be applied at runtime as the where clause depended upon the field values entered in the criteria.  We could use named bind variables but what happens if the search is executed again ?

The solution:  In case we use named bind variables and add them to the where clause, executing the query again with different criteria will cause JBO exception, because the bind variable will be there but it obviously would not have any value. To fix this, the simple solution then is to remove all bind variables at the start of your query and then add them depending upon the conditions. The code snippet below shows how to handle this.

//In the backing bean

public void doSearch(ActionEvent ae){
DCBindingContainer bc=(DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
DCIteratorBinding iteratorBinding=bc.findIteratorBinding("YOUR_VO");
ViewObject voImpl=iteratorBinding.getViewObject();
//Get all the bind variables that we had added
AttributeList whereClauseParams=voImpl.getNamedWhereClauseParams();
//Get their names
String []attribNames=whereClauseParams.getAttributeNames();
for(String attribName: attribNames){
voImpl.removeNamedWhereClauseParam(attribName);
}
StringBuilder whereClause=new StringBuilder();
//dummy
whereClause.append("where 1=1 ");
//now append named bind variables to query
if(empId==1){
//a like comparison bind variable
whereClause.append("and empName like upper(:bEmpName)||'%'");
}
//define the created named bindvariable
vo.defineNamedWhereClauseParam("bEmpName",empName,null);
//invoke am's method to execute the query ?
.....
// In AM
query=query+whereClause;
//execute our defined query
voImpl.setFullSqlMode(ViewObjectImpl.FULLSQL_MODE_AUGMENTATION);
voImpl.setQuery(query);
voImpl.executeQuery();


   



That’s it now you can run your dynamic queries without bugging your DBA.

Posted on Sunday, October 05, 2014 by Unknown

Oct 4, 2014

Retrofit is a type-safe rest client for java/android that one can use for easily consuming rest services. It supports both synchronous and asynchronous retrieval. In this post, I will explain an example which shows how you can integrate it with your android application for consuming rest services asynchronously.  The example will show its integration for doing feed search with Feedly. Feedly’s basic feed search is free, you can find the document here Feedly Search Service. 

Setting up Retrofit : -

I am now using android studio instead of eclipse as android studio is based on IntelliJ Idea platform and performs much better in every aspect.  In your build add the following Gradle dependency, rest will be managed by Gradle.

 compile 'com.squareup.retrofit:retrofit:1.6.1'


About Feedly’s feed search API :-



Feedly's  search feed API has the following request signature. GET /v3/search/feeds It accepts the following three parameters: query Required string search query. Can be a feed url, a site title, a site url or a #topic count Optional number number of results. default value is 20 locale Optional locale hint the search engine to return feeds in that locale (e.g. “pt”, “fr_FR”).



A sample request response is shown below:-



Request:



GET feedly.com/v3/search/feeds?query=ramannanda.blogspot.com



Response:



{"queryType":"term","results":[{"deliciousTags":["adf"],"feedId":"feed/http://ramannanda.blogspot.com/feeds/posts/default","language":"en","subscribers":23,"title":"Technical Essentials","velocity":0.0,"lastUpdated":1402601340000,"website":"http://ramannanda.blogspot.com/","score":2.299999952316284,"estimatedEngagement":5,"description":"Java, ADF, Identity Management, Fusion Middleware, Linux,  Counter Strike 1.6, BSD, Windows, \nProgramming, Search Engines","scheme":"TEXT:BASELINE:ORGANIC_SEARCH","contentType":"longform","partial":false,"twitterScreenName":"ramannanda9","twitterFollowers":75,"facebookUsername":"technicalessentials9911","facebookLikes":3,"iconUrl":"http://storage.googleapis.com/site-assets/WkQvKWtcgur8yMCOJbBWti9mkZPUEZGutfEwCkYH6Pc_icon-1484cef868f","visualUrl":"http://storage.googleapis.com/site-assets/WkQvKWtcgur8yMCOJbBWti9mkZPUEZGutfEwCkYH6Pc_visual","coverUrl":"http://storage.googleapis.com/site-assets/WkQvKWtcgur8yMCOJbBWti9mkZPUEZGutfEwCkYH6Pc_cover","coverColor":"C0DEED"}],"related":["adf"],"scheme":"subs.0"}


Using Retrofit:



Now we need to setup the service interface which can be used to call this service and implement a callback to which the results will be assigned.  



Search Interface:



public interface FeedlySuggestionsService {
@GET("/v3/search/feeds")
void searchFeeds(@QueryMap Map<String, String> options, Callback<FeedlyResponse> cb);
}


The interface is pretty easy to understand.




  • It targets search feeds service as explained above.


  • The searchFeeds method accepts a map of key-value request parameters.


  • It also specifies a parameter that is of callback interface, which we need to implement in an activity or fragment to  use the results.


  • Notice that FeedlyResponse is a custom class and retrofit will parse the JSON and populate the results.  Pretty neat! 



The POJO classes for response are mentioned below:



The FeedlyResponse class:-



public class FeedlyResponse {
public FeedlyResponse(){
}

public List<FeedlyResult> getResults() {
return results;
}

public void setResults(List<FeedlyResult> results) {
this.results = results;
}

private List<FeedlyResult> results;

}


The FeedlyResult class.



public class FeedlyResult {

private String feedId;
private String title;
private String coverUrl;
private String visualUrl;
private String iconUrl;

public String getSubscribers() {
return subscribers;
}

public void setSubscribers(String subscribers) {
this.subscribers = subscribers;
}

private String subscribers;
public String getFeedId() {
return feedId;
}

public void setFeedId(String feedId) {
this.feedId = feedId;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getCoverUrl() {
return coverUrl;
}

public void setCoverUrl(String coverUrl) {
this.coverUrl = coverUrl;
}

public String getVisualUrl() {
return visualUrl;
}

public void setVisualUrl(String visualUrl) {
this.visualUrl = visualUrl;
}

public String getIconUrl() {
return iconUrl;
}

public void setIconUrl(String iconUrl) {
this.iconUrl = iconUrl;
}


}


If you see the JSON response, you will see that  FeedlyResponse class maps to the response object that is returned and List<FeedlyResult> object maps to the results array. As you can see this is pretty cool, because retrofit  will  populate only the parameters that you specify.



In your activity/fragment:-



You just need to implement a method which fetches the data as shown below.



private void loadData(String searchText) {
//specify endpoint and build the restadapter instance
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://feedly.com")
.build();
//Now use restadapter to create an instance of your interface
FeedlySuggestionsService searchService=restAdapter.create(FeedlySuggestionsService.class);
//populate the request parameters
HashMap queryMap=new HashMap();
queryMap.put("query",searchText);
//implement the Callback<T> interface for retrieving the response
searchService.searchFeeds(queryMap, new Callback<FeedlyResponse>() {
@Override
public void success(FeedlyResponse feedlyResponse, Response response) {
//convert list to cursor. more on this later
MatrixCursor matrixCursor= convertToCursor(feedlyResponse.getResults());
mSearchViewAdapter.changeCursor(matrixCursor);
}

@Override
public void failure(RetrofitError error) {
Log.e(tag, error.toString());
}
});
}


I think the comments on the method should suffice, but let me explain what’s happening in the above method.




  • First, we are using the builder pattern to build the RestAdapter. Here, we are specifying the endpoint to use. 


  • We use the restadapter to create an instance for the interface we had created.


  • Implement the callback interface’s success and failure methods.


Notice that we did not have to parse the JSON to map the parameters to our POJO class, it was done automatically and asynchronous capability was added just by declaration of the callback parameter.



Have fun and let me have some REST!

Posted on Saturday, October 04, 2014 by Unknown

Oct 3, 2014

Android has  a different paradigm for application development. So , In this post, I will  explain a few of those concepts (as I understand) that lie at its core.

  1. Cursor:  To draw parallels,  a cursor object is just like a ResultSet in JDBC. It is used for iterating over the underlying data and just like a JDBC ResultSet object, it must also be closed.  The following snippet shows how to iterate over the  result set.
    try {
    cursor = getDatabase().query(tableName,
    columns, whereClause, whereArgs, groupBy, having, orderBy);
    if (cursor != null) {

    while (cursor.moveToNext()) {
    cursor.getString(cursor.getColumnIndexOf("COLUMN_NAME_TO_SELECT"));

    }
    }
    } catch (Exception e) {
    Log.e("[" + tag+ "] ", "Exception Occured while fetching data", e);
    throw e;

    } finally {
    // make sure to close the cursor
    cursor.close();

    }



  2. Loader :  if you are aware of swing development, then you will know that the best practice is to perform long running tasks such as running a query, fetching data from a service, etc. on a separate thread, this is even more important to follow in android because if you  don’t your app will just crash.  A loader is basically what you use to perform those long running tasks .  So this is what you basically have to do,  In your activity or fragment implement the Loader callback methods,  which would be invoked when the loader has finished performing its long running tasks.  The long running tasks are performed in the loader class’s  loadinbackground method.  


    /**
    The loader class
    */
    public class ArticleLoader extends AsyncTaskLoader<Map<String,String>> {

    /**
    * This is where the articles are loaded
    */
    @Override
    public Map<String,String> loadInBackground() {
    //Perform the data fetch here
    }

    }





    //the fragment class where you create the loader and wait for the loader to do its job
    public class ArticleFragment extends Fragment implements
    LoaderCallbacks<Object>{
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
    //See if the loader is there
    Loader<Object> loader = getActivity().getSupportLoaderManager().getLoader(LOADER_ARTICLE_SUMMARY);
    if (loader != null && !loader.isReset()) {
    //reset it as data needs to be loaded again because activity was created again
    getActivity().getSupportLoaderManager().restartLoader(LOADER_ARTICLE_SUMMARY, args, this);
    } else {
    //instantiate the loader
    getActivity().getSupportLoaderManager().initLoader(LOADER_ARTICLE_SUMMARY, args, this);
    }

    }
    @Override
    public Loader<Object> onCreateLoader(int id, Bundle args) {
    //instantiate the loader here depending upon loader id there could be several loaders that you might instantiate
    if(id==LOADER_ARTICLE_SUMMARY){
    String rawQuery="select _id , "+ FeedSQLLiteHelper.COLUMN_ARTICLE_TITLE+ " , "+
    FeedSQLLiteHelper.COLUMN_ARTICLE_READ+ " , "+ FeedSQLLiteHelper.COLUMN_ARTICLE_IS_FAVORITE
    +" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_DOWNLOADED+ " , "+ FeedSQLLiteHelper.COLUMN_ARTICLE_SUMMARY
    +" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_FULL_CONTENT+" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_CREATED_DATE
    +" , "+FeedSQLLiteHelper.COLUMN_ARTICLE_URL +" from "+FeedSQLLiteHelper.TABLE_ARTICLES + " where _id = ?";
    String articleId= args.getString(FeedSQLLiteHelper.COLUMN_ID);
    Loader loader=new SQLiteCursorLoader(this.getActivity().getApplicationContext(),fdb.getDbHelper(), rawQuery, new String []{articleId});
    return loader;
    }
    else {
    Loader loader= new ArticleLoader(getActivity().getApplicationContext(),id,args);
    return loader;
    }
    }
    @Override
    public void onLoadFinished(Loader<Object> loader, Object data) {
    /** Here you are supplied with the data, so you could update the cursor of the adapter
    Just make sure you check the loader id before updating the adapter
    */
    int id=loader.getId();
    if(id==LOADER_ARTICLE_SUMMARY){
    Cursor cursor=(Cursor)data;
    if(!cursor.isClosed()){
    //do pojo conversion
    article=cursorToFeedArticle(cursor);
    loadDataIntoWebView(article);

    }
    }
    else{
    Map<String,String> result=(Map)data;
    article.setArticleContent(result.get("ARTICLE_FULL_CONTENT"));
    article.setArticleDownloaded(result.get("ARTICLE_DOWNLOADED"));
    loadDataIntoWebView(article);
    }
    }
    @Override
    public void onLoaderReset(Loader<Object> arg0) {
    /** remove references to the data of the loader if its a cursor just dereference it
    * In this case since we do not have a cursor leave it empty.
    */


    }

    }



  3. Adapter:  This is used to hold the data to view binding. In other words, this object holds data which can be accessed by the view and the data to view binding information which tells how that data is mapped to the  view components of the view.


  4. Activity: An activity is like a page. It represents a view which might be composed of several child views (fragments) or just hold its own view.


  5. Fragment:  It is a child view, it has its own layout, but it exists only within an activity. As a fragment can be reused within different activities, so it should not hold any activity specific references.  You can attach the fragment to an activity at runtime using FragmentManager API’s , but if the fragment is listed in the activity’s layout then it cannot be removed. So, generally it is better to use the FragmentManager API’s then to have this restriction.




    articleFragment = (ArticleFragment) getSupportFragmentManager()
    .findFragmentByTag(ArticleFragment.tag);

    if (articleFragment == null) {
    //create a new instance of the fragment
    articleFragment = new ArticleFragment();
    Bundle args = new Bundle();
    args.putString("KEY","VALUE");
    articleFragment.setArguments(args);
    //replace the framelayout with the article fragment's content
    getSupportFragmentManager()
    .beginTransaction()
    .replace(R.id.content_frame_article_fragment,
    articleFragment, ArticleFragment.tag).commit();
    }

    else {
    // create a method onUpdateFromActivity, just supply it with new arguments
    articleFragment.onUpdateFromActivity(args);
    }



    Also Fragment instances can be retained, but in that case its lifecycle is different. It is better to have them retained because obviously it speeds up your application.


  6. Intent and Intent Filter: Intent is basically an object which is used to invoke another activity, a service instance, etc. You generally supply it with an action when invoking IntentService instance.   IntentFilter on the other hand is used to filter out what actions can a particular service, activity or a fragment can handle.


  7. IntentService and BroadcastReceiver:  IntentService listens for intent actions and maintains a queue of requests which are handled one after the other sequentially. It runs on a separate background thread, so one can perform synchronous tasks on it.  You must implement its onHandleIntent method to perform the tasks required.  Once the task is completed, you should broadcast an intent signifying so and in your activity or fragment listen for the intent using BroadcastReceiver. 


    //1. start an intentservice
    Intent feedPullServiceIntent = new Intent(this.getActivity().getApplicationContext(), PullFeedsService.class);
    feedPullServiceIntent.setAction(ACTION_UPDATE_FEEDS);
    feedPullServiceIntent.putExtra("feedName", feedName);
    getActivity().getApplicationContext().startService(feedPullServiceIntent);


    public class PullFeedsService extends IntentService {

    //2. handle the action
    @Override
    protected void onHandleIntent(Intent intent) {
    if(action==ACTION_UPDATE_FEEDS){
    if(isConnected()){
    //fetch and parse the feeds
    .....

    //3. notify completion
    notifyFetchComplete(ACTION_FEED_FETCH_COMPLETE,feedName);
    }

    }
    if(action==DELETE_FEEDS){
    //delete the feeds in the batch
    notifyFetchComplete(ACTION_DELETE_COMPLETE,feedName);
    }
    }
    private void notifyFetchComplete(String action, String feedName) {
    Intent i = new Intent();
    i.setAction(action);
    Bundle result=new Bundle();
    if(action==ACTION_FEED_FETCH_COMPLETE){
    Bundle result=new Bundle();
    result.putString("KEY", "VALUE");
    }
    else if(action==ACTION_DELETE_COMPLETE){
    Bundle result=new Bundle();
    result.putString("KEY", "VALUE");
    }
    i.putExtras(result);
    //4. Brodcast the event
    LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(i);
    }

    //in your fragment or activity

    private BroadcastReceiver receiver = new BroadcastReceiver() {
    //5. called only when the fragment receives the specific intents as specificied in the intent filter while registering
    @Override
    public void onReceive(Context context, Intent intent) {

    if(intent.getAction()==PullFeedsService.ACTION_FEED_FETCH_COMPLETE){
    if(refreshItem!=null){
    refreshItem.getActionView().clearAnimation();
    refreshItem.setActionView(null);
    }
    Bundle result=intent.getExtras();
    String refreshForFeed=result.getString("RefreshForFeed");
    if(refreshForFeed.equals("ALL")||refreshForFeed.equals(feedName)){
    //reset loaders
    resetLoaders();
    }


    }

    }
    };

    @Override
    public void onPause() {
    ...
    //fragment paused so unregister the receiver
    LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(receiver);

    }

    @Override
    public void onResume() {
    ...
    //0. fragment running so register the receiver to listen for events
    //specify a particular intent filter
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(PullFeedsService.ACTION_FEED_FETCH_COMPLETE);
    intentFilter.addAction(PullFeedsService.ACTION_NOT_CONNECTED);
    LocalBroadcastManager.getInstance(this.getActivity().getApplicationContext()).registerReceiver(receiver, intentFilter);

    }



That’s it for now. In future, I might expound upon these individual elements,.

Posted on Friday, October 03, 2014 by Unknown

Just an update : these days, I am developing a RSS reader app on android. I am using android framework for development and not  Oracle MAF because it tends to be free of license restrictions, plus it performs better. I am not particularly fond of android’s ContentProvider API’s , as I find them to be inflexible, so I am going to take the conventional approach of programming.  In the coming posts, I am going to share tips and tricks on android application development  and of course  when the application is developed(which will take some time) it will be available on play store for free with its source posted on GitHub.

Posted on Friday, October 03, 2014 by Unknown