Porting an iOS Objective-c app to Android Java [closed] - android

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 7 years ago.
Improve this question
My app is a medical data viewer, where patients wear a sensor that transmits data by Bluetooth low energy. The app was developed in Objective C, targeting the iOS platform. Now the app needs to be ported to the Android platform.
The current design and implementation for iOS is as follows:
communication - Objective C, specific to the Core Bluetooth API
data/persistence - Objective C, using FMDatabase as the interface to SQLite
algorithms/logic - Objective C
ui - JavaScript/HTML5 based on Phonegap
Since the communication is specific to the Core Bluetooth API, it will have to be re-written for Android. The ui layer should be readily portable without much change as it fully depndendt on Phonegap. Yet for the persistence and logic layers I am looking for a way to either convert them automatically to Android, or re-write them in such a way that they are reusable for both platforms.
What is the best software engineering approach to implement a cross-platform app like this?

Seems like there is:
http://code.google.com/p/objc2j/
The repository should be accesible via http://objc2j.googlecode.com/svn/
Didn't check it myself, so please post your opinion on this.

Google has some open source projects that do this.
You will need to use SVN to access these repositories. Here are the links:
Java to Objective C: http://code.google.com/p/j2objc/
Objective C to Java : http://code.google.com/p/objc2j/
Good luck!

Your best bet is to use Apportable. It's a platform that provides a port of clang, the objective-c runtime, and most of the frameworks on iOS (including UIKit).
There isn't a Core Bluetooth wrapper yet but you can call the java APIs from their platform for that. FMDatabase will work fine and the Phone gap interface should in theory work fine.
I would avoid the code generators suggestions though. They will end up eating a lot of time reimplement everything you already built if you have a type of significate code base.

I've used O2J - Objective-C to Java Converter for a similar scenario and it worked very well.
It will do a great job on your algorithms/logic without much work.
It's customizable so you can add you own translations for your Bluetooth code. You may be able to get by translating the bluetooth method calls directly to java if the APIs work the same but they probably don't. It's best to have a layer of indirection in your Objective-C code for the bluetooth to make it really easy to supply a Android specific implementation. For example create a BluetoothHelper.m and a BluetoothHelper.java and the translation will go much smoother.
I have used it for projects which used FMDatabase. For the FMDatabase part we already have FMDatabase/FMResultSet as the layer of indirection! I implemented FMDatabase/FMResultSet myself since the API for sqlite Objective-c (c based sqlite functions) is too different from Android. O2J helped me get started on translating FMDatabase/FMResultSet and this is what I ended up with...
FMDatabase:
public class FMDatabase
{
private SQLiteDatabase database;
private HashMap<String, SQLiteStatement> compiled;
public FMDatabase(SQLiteDatabase database)
{
this.database = database;
}
public FMResultSet executeQuery_arguments(String sql, Object... args)
{
synchronized (database)
{
String[] selectionArgs = objectArgsAsStrings(args);
Cursor rawQuery = database.rawQuery(sql, selectionArgs);
return new FMResultSet(rawQuery);
}
}
public FMResultSet executeQuery(String sql, Object... args)
{
synchronized (database)
{
String[] selectionArgs = objectArgsAsStrings(args);
Cursor rawQuery = database.rawQuery(sql, selectionArgs);
return new FMResultSet(rawQuery);
}
}
public String debugQuery(String sql, Object...args)
{
StringBuilder sb = new StringBuilder();
FMResultSet rs = executeQuery(sql, args);
rs.setupColumnNames();
HashMap names = rs.columnNameToIndexMap();
Set ks = names.keySet();
for (Object k : ks)
{
sb.append(k);
sb.append("\t");
}
sb.append("\n");
while(rs.next())
{
for (Object k : ks)
{
String key = k.toString();
if(rs.getType(key) == Cursor.FIELD_TYPE_STRING)
{
sb.append(rs.stringForColumn(key));
}
else if(rs.getType(key) == Cursor.FIELD_TYPE_INTEGER)
{
sb.append(rs.longForColumn(key));
}
else if(rs.getType(key) == Cursor.FIELD_TYPE_FLOAT)
{
sb.append(rs.doubleForColumn(key));
}
else if(rs.getType(key) == Cursor.FIELD_TYPE_BLOB)
{
sb.append(rs.stringForColumn(key));
}
else
{
sb.append("<NOT STRING>");
}
sb.append("\t");
}
sb.append("\n");
}
return sb.toString();
}
public String[] objectArgsAsStrings(Object... args)
{
String[] selectionArgs = new String[args.length];
for (int i = 0; i < args.length; i++)
{
Object o = args[i];
if(o instanceof Date)
{
selectionArgs[i] = Long.toString(((Date) o).getTime());
}
else if(o instanceof Boolean)
{
selectionArgs[i] = ((Boolean) o).booleanValue() ? "TRUE" : "FALSE";
}
else
{
selectionArgs[i] = args[i] == null ? "" : o.toString();
}
}
return selectionArgs;
}
public boolean executeUpdate_arguments(String sql, Object... args)
{
synchronized (database)
{
String[] selectionArgs = objectArgsAsStrings(args);
database.execSQL(sql, selectionArgs);
return true;
}
}
public boolean executeUpdate(String sql, Object... args)
{
synchronized (database)
{
SQLiteStatement statement = bindToCachedCompiledStatement(sql, args);
statement.execute();
return true;
}
}
private SQLiteStatement bindToCachedCompiledStatement(String sql, Object... args)
{
HashMap<String, SQLiteStatement> statments = getCompiledStatements();
SQLiteStatement statement = statments.get(sql);
if (statement == null)
{
statement = database.compileStatement(sql);
statments.put(sql, statement);
}
statement.clearBindings();
// bindAllArgsAsStrings(statement, objectArgsAsStrings(args));
bindAllArgs(statement, args);
return statement;
}
private void bindAllArgs(SQLiteStatement statement, Object[] bindArgs)
{
if (bindArgs == null)
{
return;
}
int size = bindArgs.length;
for (int i = 0; i < size; i++)
{
Object arg = bindArgs[i];
int index = i + 1;
if(arg == null)
{
statement.bindNull(index);
}
else if (arg instanceof String)
{
statement.bindString(index, (String) arg);
}
else if (arg instanceof Double || arg instanceof Float)
{
Number numArg = (Number) arg;
statement.bindDouble(index, numArg.doubleValue());
}
else if (arg instanceof Integer || arg instanceof Long)
{
Number numArg = (Number) arg;
statement.bindDouble(index, numArg.longValue());
}
else
{
statement.bindString(index, arg.toString());
}
}
}
public long executeInsert(String string, Object... args)
{
synchronized (database)
{
SQLiteStatement statement = bindToCachedCompiledStatement(string, args);
try
{
return statement.executeInsert();
}
catch (Exception e)
{
Log.i("STD", "No Rows inserted", e);
return 0;
}
}
}
public void bindAllArgsAsStrings(SQLiteStatement statement, String[] bindArgs)
{
if (bindArgs == null)
{
return;
}
int size = bindArgs.length;
for (int i = 0; i < size; i++)
{
statement.bindString(i + 1, bindArgs[i]);
}
}
private HashMap<String, SQLiteStatement> getCompiledStatements()
{
if (compiled == null)
{
compiled = new HashMap<String, SQLiteStatement>();
}
return compiled;
}
public boolean rollback()
{
synchronized (database)
{
database.execSQL("ROLLBACK;");
}
return true;
}
public boolean commit()
{
synchronized (database)
{
database.execSQL("COMMIT;");
}
return true;
}
public boolean beginDeferredTransaction()
{
synchronized (database)
{
database.execSQL("BEGIN DEFERRED TRANSACTION;");
}
return true;
}
public boolean beginTransaction()
{
synchronized (database)
{
database.execSQL("BEGIN EXCLUSIVE TRANSACTION;");
}
return true;
}
public boolean open()
{
return true;
}
public void setShouldCacheStatements(boolean shouldCacheStatements)
{
// TODO
}
}
FMResultSet:
public class FMResultSet
{
private boolean columnNamesSetup;
private HashMap<String, Number> columnNameToIndexMap;
private Cursor rawQuery;
public FMResultSet(Cursor rawQuery)
{
this.rawQuery = rawQuery;
}
public void close()
{
rawQuery.close();
}
public void setupColumnNames()
{
if (columnNameToIndexMap == null)
{
this.setColumnNameToIndexMap(new HashMap());
}
int columnCount = rawQuery.getColumnCount();
int columnIdx = 0;
for (columnIdx = 0; columnIdx < columnCount; columnIdx++)
{
columnNameToIndexMap.put(rawQuery.getColumnName(columnIdx).toLowerCase(), new Integer(columnIdx));
}
columnNamesSetup = true;
}
public boolean next()
{
return rawQuery.moveToNext();
}
public int columnIndexForName(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
columnName = columnName.toLowerCase();
Number n = columnNameToIndexMap.get(columnName);
if (n != null)
{
return NumberValueUtil.intVal(n);
}
Log.i("StdLog", String.format("Warning: I could not find the column named '%s'.", columnName));
return -1;
}
public int intForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return 0;
}
return intForColumnIndex(columnIdx);
}
public int intForColumnIndex(int columnIdx)
{
return rawQuery.getInt(columnIdx);
}
public long longForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return 0;
}
return longForColumnIndex(columnIdx);
}
public long longForColumnIndex(int columnIdx)
{
return (long) rawQuery.getLong(columnIdx);
}
public boolean boolForColumn(String columnName)
{
return (this.intForColumn(columnName) != 0);
}
public boolean boolForColumnIndex(int columnIdx)
{
return (this.intForColumnIndex(columnIdx) != 0);
}
public double doubleForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return 0;
}
return doubleForColumnIndex(columnIdx);
}
public double doubleForColumnIndex(int columnIdx)
{
return rawQuery.getDouble(columnIdx);
}
public String stringForColumnIndex(int columnIdx)
{
return rawQuery.getString(columnIdx);
}
public String stringForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return null;
}
return this.stringForColumnIndex(columnIdx);
}
public Date dateForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return null;
}
return new Date((this.longForColumn(columnName)));
}
public Date dateForColumnIndex(int columnIdx)
{
return new Date((this.longForColumnIndex(columnIdx)));
}
public byte[] dataForColumn(String columnName)
{
if (!columnNamesSetup)
{
this.setupColumnNames();
}
int columnIdx = this.columnIndexForName(columnName);
if (columnIdx == -1)
{
return null;
}
return this.dataForColumnIndex(columnIdx);
}
public byte[] dataForColumnIndex(int columnIdx)
{
return rawQuery.getBlob(columnIdx);
}
public HashMap columnNameToIndexMap()
{
return columnNameToIndexMap;
}
public void setColumnNameToIndexMap(HashMap value)
{
columnNameToIndexMap = value;
}
#SuppressLint("NewApi")
public int getType(String string)
{
return rawQuery.getType(columnIndexForName(string));
}
}

Related

Android Studio and JavaFx

Hi im Java\JavaFx\Android-Studio beginner
i wrote a little app in JavaFx, it works fine, now i try to run it on a Smartphone with Android Studio.
Most of this code Works, but i have a problem with my for Loop, it doesnt work, and i dont know why :(
The foundWords List remains empty...
But on my Source Projekt in JavaFx it still works.
public class MainActivity extends AppCompatActivity {
String word;
String list;
char[] wordChars;
List<String> wordList = new ArrayList<String>();
EditText input;
TextView output;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
input = (EditText) findViewById(R.id.input);
output = (TextView) findViewById(R.id.output);
}
/*
Read Textfile
*/
public void readTextfile() {
BufferedReader read = null;
try {
read = new BufferedReader(
new InputStreamReader(getAssets().open("test.txt")));
Arrays.asList(wordList);
while ((list = read.readLine()) != null) {
wordList.add(list);
}
read.close();
} catch (Exception e) {
e.getMessage();
}
Log.i("ListRead", "Done"); }
public static int getNumValue(char[] string, char val) {
int count = 0;
for (char c : string) {
if (c == val) {
count++;
}
}
return count;
}
public static boolean notTooManyLetters(char[] word, char[] wordChars) {
for (char letter : wordChars) {
if (getNumValue(word, letter) > getNumValue(wordChars, letter)) {
return false;
}
}
return true;
}
public static boolean sameLetters(char[] w, char[] wordChars) {
for (char letter : w) {
if (new String(wordChars).indexOf(letter) < 0) {
return false;
}
}
return true;
}
// Create Result
public void compare() {
List<String> foundWords = new ArrayList<String>();
word = input.getText().toString().toUpperCase();
wordChars = word.toCharArray();
for (String w : wordList) {
for (char letter : wordChars) {
if (word.indexOf(letter) > 0 && foundWords.indexOf(w) < 0 && w.length() <= wordChars.length
&& notTooManyLetters(w.toCharArray(), wordChars) && sameLetters(w.toCharArray(), wordChars)) {
foundWords.add(w);
} else {
if (foundWords.size() <= 0) {
Log.i("CheckList", "Empty List!");
}
}
}
}
output.setText(foundWords.toString());
}
public void onKlickAbc(View view) {
readTextfile();
compare();
}}
It seems like your code simply never executes.
Your activity overrides lifecycle methods and those are the only methods the OS is going to call. Any other methods are useless if not linked or triggered from these lifecycle methods.
Indeed, in Android you don't have access to a "main loop" like in other UI frameworks. Please refer to https://developer.android.com/guide/components/activities/intro-activities#mtal
PS: welcome to SO :)

How do I update my RecyclerViewAdapter correctly when I have added, removed and moved items?

I was looking for a library, gist, snippet or best practice to help me update my RecyclerView.Adapter when I receive a list of items where some items are newly added, some have moved, some are kept and some have been removed. I didn't want to use notifyDatasetChanged, because I wanted the moved items to be visually moved.
I couldn't find anything. So I ended up doing it manually by my self.
Two things:
1. Is there a a library, gist, snippet or best practice?
2. Could you guys give feedback on my solution? Is it awful? What can I do different? Would you use it?
I tried to do my solution as generic as possible. You can use it two ways; make it put the items into the adapter and update it, or it can return a list of status's for each item, so that you can handle the updates yourself.
public class RecyclerViewAdapterItemsUpdatedHelper<T, A extends RecyclerView.Adapter>{
List<T> currentItems;
List<T> newItems;
public RecyclerViewAdapterItemsUpdatedHelper(
List<T> currentItems,
List<T> newItems) {
this.currentItems = currentItems;
this.newItems = newItems;
}
public enum State {
ADDED, MOVED, REMOVED, KEPT
}
public boolean helpMeHandleIt(A adapter) {
log(currentItems, newItems);
boolean moved = false;
boolean added = false;
boolean removed = false;
for (int i = currentItems.size() -1; i > -1; i--) {
if (!newItems.contains(currentItems.get(i))) {
currentItems.remove(i);
adapter.notifyItemRemoved(i);
removed = true;
}
}
for (int i = 0; i < newItems.size(); i++) {
State state;
try {
if (!newItems.get(i).equals(currentItems.get(i))) {
state = currentItems.contains(newItems.get(i)) ? State.MOVED : State.ADDED;
if (state.equals(State.ADDED)) {
currentItems.add(i, newItems.get(i));
adapter.notifyItemInserted(i);
added = true;
} else if (state.equals(State.MOVED)) {
int from = currentItems.indexOf(newItems.get(i));
currentItems.remove(from);
currentItems.add(i, newItems.get(i));
adapter.notifyItemMoved(from, i);
moved = true;
}
}
} catch (IndexOutOfBoundsException e) {
currentItems.add(i, newItems.get(i));
adapter.notifyItemInserted(i);
added = true;
}
}
return moved && !added && !removed;
}
public List<StatusItem> justGiveMeStatus() {
log(currentItems, newItems);
List<StatusItem> statusItems = new ArrayList<>();
for (int i = currentItems.size() -1; i > -1; i--) {
if (!newItems.contains(currentItems.get(i))) {
statusItems.add(new StatusItem(currentItems.get(i), State.REMOVED, i, -1));
}
}
for (int i = 0; i < newItems.size(); i++) {
State state;
try {
if (!newItems.get(i).equals(currentItems.get(i))) {
state = currentItems.contains(newItems.get(i)) ? State.MOVED : State.ADDED;
if (state.equals(State.ADDED)) {
statusItems.add(new StatusItem(newItems.get(i), State.ADDED, -1, i));
} else if (state.equals(State.MOVED)) {
int from = currentItems.indexOf(newItems.get(i));
statusItems.add(new StatusItem(newItems.get(i), State.MOVED, from, i));
}
} else {
statusItems.add(new StatusItem(newItems.get(i), State.KEPT, i, i));
}
} catch (IndexOutOfBoundsException e) {
statusItems.add(new StatusItem(newItems.get(i), State.ADDED, -1, i));
}
}
return statusItems;
}
private void log(List<T> currentItems, List<T> newItems) {
for (T obj :
currentItems) {
Log.i("current item", obj.toString());
}
for (T obj :
newItems) {
Log.i("new item", obj.toString());
}
}
public class StatusItem {
private T item;
private State status;
private int from = -1;
private int to = -1;
public StatusItem(T item, State status) {
this.item = item;
this.status = status;
}
public StatusItem(T item, State status, int from, int to) {
this.item = item;
this.status = status;
this.from = from;
this.to = to;
}
public T getItem() {
return item;
}
public State getStatus() {
return status;
}
public int getFrom() {
return from;
}
public int getTo() {
return to;
}
#Override
public String toString() {
return item.toString() + " | " + status +
" | " + from + " -> " + to;
}
}

ArrayList.indexOf() is not recognizing Objects

I have a ParseObject subclass , but everytime I want to get index of it it returns 0 so mListSectionPos returns an array of zero's (hachCode and equals methd implemented thanks to Apache Commons Utils).
It should be String.valueOf(mListItems.indexOf(beer_section)), but instead I'm using mListSectionPos.add(mListItems.indexOf(current_item) - 1); because it's working (more or less). Sometimes it cracks on getCurrentSectionPosition() that also works on indexOf() method.
So my question is: why indexOf() always return 0 in this piece of code?
It's based on https://github.com/bhavyahmehta/ListviewFilter - just adapted for ParseObject lists. Code below is my adaptation of his MainActivity.java that can be found here:
#Override
protected Void doInBackground(ArrayList<PiwoSubclass>... params) {
mListItems.clear();
mListSectionPos.clear();
ArrayList<PiwoSubclass> items = params[0];
if(mItems != null) {
if (mItems.size() > 0) {
String prev_section = "";
for (PiwoSubclass current_item : items) {
if (isCancelled()) break;
String current_section = current_item.getBeerName().substring(0, 1).toUpperCase(Locale.getDefault());
if (!prev_section.equals(current_section)) {
PiwoSubclass beer_section = null;
beer_section = new PiwoSubclass();
beer_section.setBeerName(current_section);
Log.i("ASD-current", beer_section.getBeerName());
mListItems.add(beer_section);
mListItems.add(current_item);
// array list of section positions
mListSectionPos.add(mListItems.indexOf(current_item) - 1); // that want works although it's way around
// TODO why is that error?
Log.i("ASD-listSectionSize", String.valueOf(mListItems.indexOf(beer_section)));
prev_section = current_section;
} else {
mListItems.add(current_item);
}
}
}
}
return null;
}
PiwoSubclass
public class PiwoSubclass extends ParseObject {
private String objectIdP;
private String marka;
private String marka_lowercase;
public PiwoSubclass() {
}
public String getObjectIdfromParse() {
return this.getObjectId();
}
public String getMarka(){
return this.getString("marka");
}
public String getBrewery(){
return this.getString("brewery");
}
public String getBeerName(){
return this.getString("beer_name");
}
public String getMarka_lowercase() {
return this.getString("marka_lowercase");
}
public void setMarka(String value){
put("marka", value);
}
public void setBeerName(String value){
put("beer_name", value);
}
public void setMarka_lowercase(String value){
put("marka_lowercase", value);
}
#Override
public int hashCode() {
return new HashCodeBuilder(17, 31) // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
.append(getObjectIdfromParse())
.toHashCode();
}
#Override
public boolean equals(Object obj) {
//return super.equals(obj);
if (!(obj instanceof PiwoSubclass))
return false;
if (obj == this)
return true;
marka_lowercase = getMarka_lowercase();
PiwoSubclass rhs = (PiwoSubclass) obj;
//Log.i("ASD-subclass", marka + "/" + rhs.getMarka());
return new EqualsBuilder()
// if deriving: appendSuper(super.equals(obj)).
.append(marka_lowercase, rhs.getMarka_lowercase())
.isEquals();
}
Now I have IndexOutOfBounds exception from PinnedHeaderAdapter:
public int getCurrentSectionPosition(int position) {
//String listChar = mListItems.get(position).getBeerName().substring(0, 1).toUpperCase(Locale.getDefault());
PiwoSubclass ps = mListItems.get(position); // TODO errorrrrrrrrr
return mListItems.indexOf(ps);
}
First, you check for mItems
if(mItems != null) {
if (mItems.size() > 0) {
but then you work with items
for (PiwoSubclass current_item : items) {
/* ... */
}
and ignore mItems for the rest of the method. I don't see any connection between these two.
It seems indexOf() doesn't return 0 but 1, otherwise you would get an ArrayList full of -1s
mListSectionPos.add(mListItems.indexOf(current_item) - 1);
I guess, somehow you always check for the first current_item, which is the second element in mListItems. If you would check for the beer_section - as it does for current_section in the original code - the code would work as expected.
After looking into ArrayList.indexOf(), the most likely reason is your PiwoSubclass.equals() method compares always equal to the first non-section element, because it hasn't set a beer name or some similar condition.
So, fixing the equals method might work as well.

How to apply multiple Filters on an Adapter?

I got a listView with and a search field that calls my Adapter's getFilter().filter(keyword) func.
It is working very nice, but i would like to add an other filter that searches in different tags of my listViews's objects.
So i need two filters for my Adapter, whats the best solution for this?
Thanks,
I suppose that you implemented the filter yourself. As you cant get two filters you could have a field in the filter that defines what kind of filtering should by applied ( you could use multiple filters in your filter).
Set you field of the filter to the value you want before using the filter.
Or:
Use the keyword to choose the filter to apply. Add on the beginning of the keyword some characters which define the filter to apply. With String.beginsWith() you can check which type of filtering has to by applied. This has to be done in the Filter itself. The caller of the getFilter.filter(keyword) has to know what characters have to by added in front of the string.
Apply Multiple Filter in Listview and also use the multi sorting in ListView, try this link:
https://github.com/apurv3039/filter_listview/tree/master
I had a similar need and I've written for myself. The filter are combined with AND operator. Keep it as simple as possible. Not claiming it is perfect but it works for me. You can change according to your need.
The ItemModel
public class ItemModel {
public int ID;
public int rating;
public float avg;
public String name;
public String shortDesc;
public boolean read;
}
And the parser.java
/**
* This class is designed to be simple for parsing a filter of the form
* "object field name: variable type: operator: value"
*
* "ID:int:>:20";"name:string:=:20"
* Where ';' means AND, however this is not parsed here.
* If you need different logic, use the results.O
*
* Multiple filters seperated by ';'
*/
public class Parser {
private static final String TAG = "Parser";
public static boolean isPassingTheFiler(String filter, String field, String val) {
String[] mGroups = parseMainGroups(filter);
for (String mGroup : mGroups) {
Log.e(TAG,"Working on the main Group " +mGroup );
String[] subCommand = parseSubCommand(mGroup);
if ( field.equals(subCommand[0])) {
if (!processCommand(subCommand, val)) {
return false;
}
}
}
return true;
}
/**
* parses that data assuming they are all sperated by `;`
*/
public static String[] parseMainGroups(CharSequence commands) {
String buffer = commands.toString();
String parts[] = buffer.split(";");
return parts;
}
public static String[] parseSubCommand(String subCommand) {
//remove the double quotes.
String buffer = removeDoubleQuotes(subCommand.toString());
String parts[] = buffer.split(":");
return parts;
}
public static String removeDoubleQuotes(String quotedString) {
if ((quotedString.charAt(0) == '\"') && (quotedString.charAt(quotedString.length() - 1) == '\"')) {
return quotedString.substring(1, quotedString.length() - 1);
} else {
Log.e(TAG, quotedString + " doesn't contained in double quotes!\nReturned empty string!!!");
return "";
}
}
public static boolean processCommand(String[] subCommand, String val) {
switch (subCommand[1]) {
case "int":
Log.e("TAG","\tint Filer");
return intStatement(subCommand, val);
case "float":
Log.e("TAG","\tfloat Filer");
return floatStatement(subCommand, val);
case "string":
Log.e("TAG","\tString Filer");
return stringStatement(subCommand, val);
default:
return false;
}
}
/**
* Evaluate the Int statement's correctness with the given INT value
*/
public static boolean intStatement(String[] subCommand, String cVal) {
String operString = subCommand[2];
int iVal;
int val;
try {
iVal = Integer.parseInt(subCommand[3]);
val = Integer.parseInt(cVal);
} catch (NumberFormatException e) {
return false;
}
switch (operString) {
case "=":
return val == iVal;
case "<":
return val < iVal;
case ">":
return val > iVal;
case "<=":
return val <= iVal;
case ">=":
return val >= iVal;
case "!=":
return val != iVal;
case "s" :
//digit search as string. We look into string from that we already have
return cVal.contains(subCommand[3]);
default:
Log.e("Parser", "invalid Integer Operation");
return false;
}
}
public static boolean floatStatement(String[] subCommand, String cVal) {
String operString = subCommand[2];
float iVal;
float val;
try {
iVal = Float.parseFloat(subCommand[3]);
val = Float.parseFloat(cVal);
} catch (NumberFormatException e) {
return false;
}
switch (operString) {
case "=":
return val == iVal;
case "<":
return val < iVal;
case ">":
return val > iVal;
case "<=":
return val <= iVal;
case ">=":
return val >= iVal;
case "!=":
return val != iVal;
case "s" :
//digit search as string. We look into string from that we already have
return cVal.contains(subCommand[3]);
default:
Log.e("Parser", "invalid Integer Operation");
return false;
}
}
public static boolean stringStatement(String[] subCommand, String val) {
String operString = subCommand[2];
switch (operString) {
case "=":
//equality
return val.equals(subCommand[3]);
case "<":
//substring
return val.contains(subCommand[3]);
case "sw":
//prefix
return val.startsWith(subCommand[3]);
case "ew":
//postfix
return val.endsWith(subCommand[3]);
default:
Log.e("Parser", "invalid Integer Operation");
return false;
}
}
}
The private filter class in the adapter.
private class ItemFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
String charString = constraint.toString();
String[] parts;
FilterResults filterResults = new FilterResults();
if (charString.isEmpty()) {
filterResults.values = new ArrayList<>(itemList);
} else {
//Parse the main group
parts = parseMainGroups(charString);
if (parts.length < 1) {
filterResults.values = new ArrayList<>(itemList);
} else {
List<ItemModel> filteredList = new ArrayList<>();
for (ItemModel row : itemList) {
if ( !isPassingTheFiler(charString,"ID",""+row.ID)) {
continue;
} else {
Log.e("Filter", "passed on ID" + row.ID);
}
if ( !isPassingTheFiler(charString,"name",""+row.name)) {
continue;
} else {
Log.e("Filter", "passed on name" + row.name);
}
// passed every test asked. If empty they returned true!
filteredList.add(row);
}
filterResults.values = new ArrayList<>(filteredList);
}
}
return filterResults;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
updateList((List<ItemModel>) results.values);
}
}
And the updateList member funtion for the adapter
public void updateList(List<ItemModel> newList) {
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ItemDiffUtilCallback(newList, itemListFiltered));
itemListFiltered.clear();
itemListFiltered.addAll(newList);
diffResult.dispatchUpdatesTo(this);
Log.e("TAG", "updated with dispatch");
}
And the difutils that helps the good animation
public class ItemDiffUtilCallback extends DiffUtil.Callback {
private List<ItemModel> oldList;
private List<ItemModel> newList;
public ItemDiffUtilCallback(List<ItemModel> newList, List<ItemModel> oldList) {
this.newList = newList;
this.oldList = oldList;
}
#Override
public int getOldListSize() {
return oldList.size();
}
#Override
public int getNewListSize() {
return newList.size();
}
#Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).ID == newList.get(newItemPosition).ID;
}
#Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));
}
#Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return super.getChangePayload(oldItemPosition, newItemPosition);
}

Reusable AsyncTask by any activity

My goal is to have an AsyncTask that
can execute multiple times (one task at a time of course)
its current task can be cancelled
can be used by any activity
can execute many different tasks
does not have any problem with screen rotation (or phonecalls etc)
To achieve that i have created the classes shown below. But my experience with (and understanding of) threads is very limited. And since i don't know of any way to debug multiple threads, there is no way (for me) of knowing if this is going to work or not. So what i'm really asking is: Is this code ok?
And since there is no code that it is currently using this, here's an example use for it:
Data2Get d2g = new Data2Get(this, Data2Get.OpCountNumbers);
d2g.setParam("up2Num", String.valueOf(800));
LongOpsRunner.getLongOpsRunner().runOp(d2g);
So, here we go. This is the interface that every activity that wants to execute a long task (operation - op) should implement:
public interface LongOpsActivity {
public void onTaskCompleted(OpResult result);
}
This is a class to enclose any result of any task:
public class OpResult {
public LongOpsActivity forActivity;
public int opType;
public Object result;
public OpResult(LongOpsActivity forActivity, int opType, Object result){
this.forActivity = forActivity;
this.opType = opType;
this.result = result;
}
}
And finally the big part, the singleton async task class:
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import android.os.AsyncTask;
public class LongOpsRunner extends AsyncTask<Void, OpResult, Void> {
public class Data2Get implements Cloneable {
// one id for each operation
public static final int OpCountNumbers = 1;
public static final int OpCountLetters = 2;
public LongOpsActivity forActivity;
public int opType;
private HashMap<String, String> params = new HashMap<String, String>();
public Data2Get(LongOpsActivity forActivity, int opType) {
this.forActivity = forActivity;
this.opType = opType;
}
public void setParam(String key, String value) {
params.put(key, value);
}
public String getParam(String key) {
return params.get(key);
}
public void clearParams() {
params.clear();
}
#Override
protected Object clone() throws CloneNotSupportedException {
// deep clone
Data2Get myClone = (Data2Get) super.clone();
myClone.clearParams();
for (Entry<String, String> entry : params.entrySet()) {
myClone.setParam(new String(entry.getKey()), new String(entry.getValue()));
}
return myClone;
}
}
private class IntermediateResult extends OpResult {
public IntermediateResult(LongOpsActivity forActivity, int opType, Object result) {
super(forActivity, opType, result);
}
}
// not really needed
private class FinalResult extends OpResult {
public FinalResult(LongOpsActivity forActivity, int opType, Object result) {
super(forActivity, opType, result);
}
}
private final ReentrantLock lock = new ReentrantLock();
private final Condition executeOp = lock.newCondition();
private volatile boolean finished = false;
private volatile boolean waiting = true;
private volatile boolean shouldCancel = false;
private volatile boolean activityHasBeenNotified = true;
private Data2Get startingOpParams = null;
private Data2Get currentOpParams = null;
private FinalResult currentOpResult;
protected Void doInBackground(Void... nothing) {
try {
lock.lockInterruptibly();
do {
waiting = true;
while (waiting) {
executeOp.await();
}
shouldCancel = false;
activityHasBeenNotified = false;
boolean opCancelled = false;
try {
currentOpParams = (Data2Get) startingOpParams.clone();
} catch (CloneNotSupportedException cns) {
// do nothing
}
switch (currentOpParams.opType) {
case Data2Get.OpCountNumbers:
int numberCounter = 0;
int numLoopCount = 0;
while ((!opCancelled) & (numLoopCount <= 5000000)) {
if (!shouldCancel) {
numberCounter = (numberCounter + 1)
% Integer.parseInt(currentOpParams.getParam("up2Num"));
if (numberCounter == 0) {
numLoopCount++;
publishProgress(new IntermediateResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Numbers loop count:" + numLoopCount));
}
} else {
opCancelled = true;
activityHasBeenNotified = true;
}
if (!opCancelled) {
currentOpResult = new FinalResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Numbers loop completed.");
publishProgress(currentOpResult);
}
}
break;
case Data2Get.OpCountLetters:
int letterLoopCount = 0;
char ch = 'a';
while (!opCancelled & (letterLoopCount <= 5000000)) {
if (!shouldCancel) {
ch++;
if (Character.toString(ch).equals(currentOpParams.getParam("up2Letter"))) {
ch = 'a';
letterLoopCount++;
publishProgress(new IntermediateResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Letters loop count:" + letterLoopCount));
}
} else {
opCancelled = true;
activityHasBeenNotified = true;
}
if (!opCancelled) {
currentOpResult = new FinalResult(
currentOpParams.forActivity,
currentOpParams.opType,
"Letters loop completed.");
publishProgress(currentOpResult);
}
}
break;
default:
}
} while (!finished);
lock.unlock();
} catch (InterruptedException e) {
// do nothing
}
return null;
}
public void cancelCurrentOp() {
shouldCancel = true;
}
#Override
protected void onProgressUpdate(OpResult... res) {
OpResult result = res[0];
if (result instanceof IntermediateResult) {
// normal progress update
// use result.forActivity to show something in the activity
} else {
notifyActivityOpCompleted(result);
}
}
public boolean currentOpIsFinished() {
return waiting;
}
public void runOp(Data2Get d2g) {
// Call this to run an operation
// Should check first currentOpIsFinished() most of the times
startingOpParams = d2g;
waiting = false;
executeOp.signal();
}
public void terminateAsyncTask() {
// The task will only finish when we call this method
finished = true;
lock.unlock(); // won't this throw an exception?
}
protected void onCancelled() {
// Make sure we clean up if the task is killed
terminateAsyncTask();
}
// if phone is rotated, use setActivity(null) inside
// onRetainNonConfigurationInstance()
// and setActivity(this) inside the constructor
// and all that only if there is an operation still running
public void setActivity(LongOpsActivity activity) {
currentOpParams.forActivity = activity;
if (currentOpIsFinished() & (!activityHasBeenNotified)) {
notifyActivityOpCompleted(currentOpResult);
}
}
private void notifyActivityOpCompleted(OpResult result) {
if (currentOpParams.forActivity != null) {
currentOpParams.forActivity.onTaskCompleted(result);
activityHasBeenNotified = true;
}
}
private static LongOpsRunner ref;
private LongOpsRunner() {
this.execute();
}
public static synchronized LongOpsRunner getLongOpsRunner() {
if (ref == null)
ref = new LongOpsRunner();
return ref;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
I hope someone helps with making this work, as it would be very useful not only for me, but many other people out there. Thank you.
Try Loaders. I switched from simple AsyncTasks to AsyncTaskLoaders and they solve lots of problems. If you implement a Loader as a standalone class, it would meet all of your requirements, especially when it comes to rotation which is the biggest issue with old AsyncTask.

Categories

Resources