Realm database - how do I structure my Android application - android

I have an application where I want to search for a book using ISBN and display it on the screen. The Book objects are stored in a Realm database with ISBN as primary key. If they are not stored in the database, they are retrieved asynchronously (ASyncTask) from a server and then stored in the database.
Now, I am unfamiliar with Realm and how to use it.
In the code below, would ViewBookActivity and MainController run on the same thread, and therefore use the same database instance? Would returning a Realm object from a "static class" be a problem?
How can I guarantee that calling MainController.getBook() always returns a book? The way it works now, is that when getBook is called and the book is not in the database, it returns null.
Is a changelisteners in each Activity that uses the MainController the only way? I want to avoid, if possible, to use/reference Realm at all in the activities and make them get objects through the MainController instead.
public class RealmActivity extends Activity {
private Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.deleteRealm(realmConfiguration); // Clean slate
Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default
}
#Override
protected void onDestroy() {
super.onDestroy();
}
}
public class ViewBookActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
ISBN = intent.getStringExtra("ISBN");
setContentView(R.layout.activity_scan_result);
setBook(ISBN);
}
public void setBook(String ISBN) {
Book b = MainController.getBook(ISBN);
// Display book on screen
}
}
public class MainController {
static Realm realm = Realm.getDefaultInstance();
public static Book getBook(String isbn) {
Book book = realm.where(Book.class)
.equalTo("isbn", isbn)
.findFirst();
if (book == null) {
NetworkController.getBook(isbn);
} else {
return book;
}
return null;
}
}
public class NetworkController {
private static BookHandler bookHandler = new BookHandler();
public static void getBook(String isbn) {
NetworkHelper.sendRequest(HTTPRequestMethod.GET,
"/books/" + isbn, bookHandler);
}
}
public class NetworkHelper {
private static String host = "http://crowdshelf-dev.herokuapp.com";
public static void sendRequest(final HTTPRequestMethod requestMethod, final String route, final ResponseHandler responseHandler) {
new AsyncTask<Void, Void, Void>() {
#Override
protected void doInBackground(Void... params) {
try {
URL url = new URL(host + "/api" + route);
Log.d("NETDBTEST", "NetworkHelper request: " + requestMethod.toString() + " URL: " + url.toString());
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
BufferedReader bReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder builder = new StringBuilder();
String line = null;
while ((line = bReader.readLine()) != null) {
builder.append(line).append("\n");
}
String jsonString = builder.toString();
responseHandler.handleJsonResponse(jsonString);
return null;
}
}
}.execute();
}
}
public class BookHandler implements ResponseHandler {
#Override
public void handleJsonResponse(String jsonString) {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.createOrUpdateObjectFromJson(Book.class, jsonString);
realm.commitTransaction();
realm.close();
}
}
public class Book extends RealmObject{
#PrimaryKey
private String isbn;
// Other fields, getters and setters
}

1) First, I don't know why you created RealmActivity just for cleaning database. Activities are for showing UI. If it doesn't have any purpose then move the code to some other class. Like you can create a new class RealmHandler and keep this kind of code there.
2) You don't really need to create a static function to get Realm object. You could have your MainController function like this:
public class MainController {
public Book getBook(String isbn, Context context) {
Realm realm = Realm.getDefaultInstance(context);
Book book = realm.where(Book.class)
.equalTo("isbn", isbn)
.findFirst();
if (book == null) {
NetworkController.getBook(isbn);
} else {
return book;
}
return null;
}
}
3)
How can I guarantee that calling MainController.getBook() always
returns a book?
You can't guarantee that you always get a Book, for example a Book isn't found on server, or there's no internet connection. You have to handle that case manually.
sendBook function calls AsyncTask and AsyncTask runs on a separate thread so you can't return your server results when you call NetworkController.getBook(isbn). The only way to get results is in BookHandler. handleJsonResponse
By the way, you don't need to implement Handler to get results from AsyncTask. You can override onPostExecution function to get control of your results and perform UI on Main thread.
Solution:
You can handle this in different ways as you like, here's one of the way:
i) Call MainController.getBook() to get a book. If Book isn't found then run NetworkController.getBook(isbn); - This will run on a different Thread and current Thread will send back null to your Activity. If null then you just ignore it or perform action accordingly.
ii) In AsyncTask you could first show a ProgressBar to tell user something is in Process while you are getting data from Server. You could do this in onPreExecution function of AsyncTask.
iii) Get results in onPostExecution function of AsyncTask. Now you are back on your main thread. You can directly update your UI if you are in the same Activity, but in your case you are not. You can't directly return data like in a function or update UI.
iv) You can send a signal to your MainActivity that network call has finished and new data is in the database so update view. You can could use Event bus for this purpose. I personally use Otto by Square: http://square.github.io/otto/

Related

SQL query with listview

I am busy with an application where i am getting data from my azure database with sql and storing it in an array. I created a separate class where i get my data and my main activity connects to this class and then displays it.
Here is my getData class:
public class GetData {
Connection connect;
String ConnectionResult = "";
Boolean isSuccess = false;
public List<Map<String,String>> doInBackground() {
List<Map<String, String>> data = null;
data = new ArrayList<Map<String, String>>();
try {
ConnectionHelper conStr=new ConnectionHelper();
connect =conStr.connectionclass(); // Connect to database
if (connect == null) {
ConnectionResult = "Check Your Internet Access!";
} else {
// Change below query according to your own database.
String query = "select * from cc_rail";
Statement stmt = connect.createStatement();
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) {
Map<String,String> datanum=new HashMap<String,String>();
datanum.put("NAME",rs.getString("RAIL_NAME"));
datanum.put("PRICE",rs.getString("RAIL_UNIT_PRICE"));
datanum.put("RANGE",rs.getString("RAIL_RANGE"));
datanum.put("SUPPLIER",rs.getString("RAIL_SUPPLIER"));
datanum.put("SIZE",rs.getString("RAIL_SIZE"));
data.add(datanum);
}
ConnectionResult = " successful";
isSuccess=true;
connect.close();
}
} catch (Exception ex) {
isSuccess = false;
ConnectionResult = ex.getMessage();
}
return data;
}
}
And in my Fragmentactivity.java I simply just call the class as shown here:
List<Map<String,String>> MyData = null;
GetValence mydata =new GetValence();
MyData= mydata.doInBackground();
String[] fromwhere = { "NAME","PRICE","RANGE","SUPPLIER","SIZE" };
int[] viewswhere = {R.id.Name_txtView , R.id.price_txtView,R.id.Range_txtView,R.id.size_txtView,R.id.supplier_txtView};
ADAhere = new SimpleAdapter(getActivity(), MyData,R.layout.list_valence, fromwhere, viewswhere);
list.setAdapter(ADAhere);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
HashMap<String,Object> obj=(HashMap<String,Object>)ADAhere.getItem(position);
String ID=(String)obj.get("A");
Toast.makeText(getActivity(), ID, Toast.LENGTH_SHORT).show();
}
});
My problem comes when I want to include the onPreExecute and onPostExecute because I am relatively new to android studio and I do not know where to put the following lines of code:
#Override
protected void onPreExecute() {
ProgressDialog progress;
progress = ProgressDialog.show(MainActivity.this, "Synchronising", "Listview Loading! Please Wait...", true);
}
#Override
protected void onPostExecute(String msg) {
progress.dismiss();
}
You need to get the data from your azure database using a background service or AsyncTask. However, you are defining a class GetData which does not extend AsyncTask and hence the whole operation is not asynchronous. And I saw you have implemented doInBackground method which is not applicable here as you are not extending AsyncTask. I would suggest an implementation like the following.
You want to get some data from your azure database and want to show them in your application. In these kind of situations, you need to do this using an AsyncTask to call the server api to get the data and pass the data to the calling activity using an interface. Let us have an interface like the following.
public interface HttpResponseListener {
void httpResponseReceiver(String result);
}
Now from your Activity while you want to get the data through an web service call, i.e. AsyncTask, just the pass the interface from the activity class to the AsyncTask. Remember that your AsyncTask should have an instance variable of that listener as well. So the overall implementation should look like the following.
public abstract class HttpRequestAsyncTask extends AsyncTask<Void, Void, String> {
public HttpResponseListener mHttpResponseListener;
private final Context mContext;
HttpRequestAsyncTask(Context mContext, HttpResponseListener listener) {
this.mContext = mContext;
this.mHttpResponseListener = listener;
}
#Override
protected String doInBackground(Void... params) {
String result = null;
try {
// Your implementation of getting data from your server
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
#Override
protected void onPostExecute(final String result) {
mHttpResponseListener.httpResponseReceiver(result);
}
#Override
protected void onCancelled() {
mHttpResponseListener.httpResponseReceiver(null);
}
}
Now you need to have the httpResponseReceiver function implemented in the calling Activity. So the sample activity should look like.
public class YourActivity extends AppCompatActivity implements HttpResponseListener {
// ... Other code and overriden functions
public void callAsyncTaskForGettingData() {
// Pass the listener here
HttpRequestAsyncTask getDataTask = new HttpRequestGetAsyncTask(
YourActivity.this, this);
getDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
#Override
public void httpResponseReceiver(String result) {
// Get the response callback here
// Do your changes in UI elements here.
}
}
To read more about how to use AsyncTask, you might consider having a look at here.

android MVP - How should the network layer be called , from model?

In MVP android i believe the network layer (retrofit , volley etc) should NOT be apart of the model. But I need a firm example on how to construct the model then. Should the model be a singleton that the network layer simply creates when api call completes ?
Lets take a look at a presenter i have for my one of my activities:
public class MainActivityPresenter implements IMainPresenterContract, Callback {
IMainActivityViewContract view;//todo set up a weak reference to View to avoid leakage
NewsService interactor;
public MainActivityPresenter(IMainActivityViewContract view, NewsService interactor) {
this.view = view;
this.interactor = interactor;
}
public void loadResource() {
interactor.loadResource();
}
public void onRequestComplete(final NewsEntities newsEntities) {
view.dataSetUpdated(newsEntities.getResults());
}
#Override
public void onResult(final NewsEntities newsEntities) {
onRequestComplete(newsEntities);
}
public void goToDetailsActivity(Result result) {
view.goToDetailsActivity(result);
}
}
So my question is about the NewsService interactor parameter i am passing into the constructor. I was assuming this should be model data and not a networking service. But what should it look like then ? Currently mine looks like this:
public class NewsService implements INewsServiceContract {
private Gson gson;
private Callback mCallback;
public NewsService() {
configureGson();
}
private static String readStream(InputStream in) {
StringBuilder sb = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
String nextLine;
while ((nextLine = reader.readLine()) != null) {
sb.append(nextLine);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
public void setCallBack(Callback cb) {
mCallback = cb; // or we can set up event bus
}
private void configureGson() {
GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
gson = builder.create();
}
#Override
public void loadResource() {
//Todo could use a loader instead help with the config change or a headless fragment
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
String readStream = "";
HttpURLConnection con = null;
try {
URL url = new URL("https://api.myjson.com/bins/nl6jh");
con = (HttpURLConnection) url.openConnection();
readStream = readStream(con.getInputStream());
} catch (Exception e) {
e.printStackTrace();
}
finally {
if(con!=null)
con.disconnect();
}
return readStream;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
NewsService.this.onRequestComplete(result);
}
}.execute();
}
private void onRequestComplete(String data) {
data = data.replaceAll("\"multimedia\":\"\"", "\"multimedia\":[]");
news.hotels.com.sample.Model.NewsEntities newsEntities = gson.fromJson(data, NewsEntities.class);
mCallback.onResult(newsEntities);
}
}
so as you can see the NewsService in this case is doing the network calls. I think i should not have passed this into the presenter. But how can the model be constructed then ? who calls the NewsService ?
UPDATE: THIS QUESTION WAS a long time ago, everyone please use clean architecture approach, and let your presenter know nothing about the network layer.
the network calls need to be in Model layer and should be triggered from the presenter. but its the Model layer who decides where to et the data and its hidden from the Presenter layer.
I myself use an interactor class to do this that is in model layer and a presenter will use this interactor to get the data and the interactor will get the data from DB or Server regarding to the situation.
look at this sample project in my repo:
https://gitlab.com/amirziarati/Echarge
I used Dagger to do DI that may confuse you. just look at packaging and how i seperate concerns between layers.
UPDATE: I used presenter to sync data from server and DB which is WRONG. the presenter should know nothing about this proccess. I didnt recognize this problem that time.

How to (RxJava) properly set conditions on which handler will process data, emitted from Observable?

So, my task is to push my device's Location info, when it changes, to the remote server Json API service. If remote server is unavailable, my DatabaseManager must save them to a local database.
Here is my Retrofit API:
public interface GpsService {
#POST("/v1/savelocationbatch")
SaveResponse saveLocationBatch(#Body LocationBatch locationBatch);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
GpsService service = retrofit.create(GpsService.class);
And a POJO class:
public class LocationBatch{
#SerializedName("LocationPointList")
ArrayList<LocationPoint> locationPointList;
#SerializedName("ClientId")
String clientId;
#SerializedName("ClientSecret")
String clientSecret;
//setter & getter
}
My LocationPoint model:
#Table(name="LocationPoints", id = "_id")
public class LocationPoint extends Model {
#SerializedName("Latitude")
#Column(name="latitude")
public Double latitude;
#SerializedName("Longitude")
#Column(name="longitude")
public Double longitude;
#SerializedName("Altitude")
#Column(name="altitude")
public Double altitude;
//... setters, getters etc
}
All of my last locations are stored in a CurrentLocationHolder singleton (for batch sending/saving to DB/emitting from Observable). It's setLocation() method updates currentLocation variable, then puts it to the locationBuffer, than checks the buffer's size, than if buffer's size exceeds my MAX_BUFFER_SIZE variable, it fires locationBufferChanged.onNext(with a copy of a locationBuffer as argument), then it clears locationBuffer...
public class CurrentLocationHolder {
private List<LocationPoint> locationBuffer =
Collections.synchronizedList(new ArrayList<>());
private LocationPoint currentLocation;
private final PublishSubject<List<LocationPoint>> locationBufferFull =
PublishSubject.create();
public Observable<List<LocationPoint>>
observeLocationBufferFull(boolean emitCurrentValue) {
return emitCurrentValue ?
locationBufferFull.startWith(locationBuffer) :
locationBufferFull;
}
public void setLocation(LocationPoint point) {
this.currentLocation = point;
locationBuffer.add(point);
if (locationBuffer.size() >= MAX_BUFFER_SIZE) {
locationBufferChanged.onNext(new ArrayList<>(this.locationBuffer));
}
locationBuffer.clear();
}
}
And here is my DatabaseManager:
public class DatabaseManager {
private Subscription locationBufferSubscription;
private static DatabaseManager instance;
public static void InitInstance() {
if (instance == null)
instance = new DatabaseManager();
}
}
public void saveToDb(ArrayList<LocationPoint> locArray){
ActiveAndroid.beginTransaction();
try {
for (int i = 0; i < locArray.size(); i++) {
locArray.get(i).save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally {
ActiveAndroid.endTransaction();
}
}
}
My application's main goal:
To write all of the listened LocationPoints to the HTTP server through Retrofit. If a remote server will be suddenly down for some reason (or internet connection would lost), my app should seamlessly write new locationPoints to a local database. When the server (or internet) will be up, some mechanism should provide saved local data to Retrofit's call.
So, my questions are:
How to create an Rx-Observable object, which will emit List normally to a Retrofit service, but when server (or internet) goes down, it should provide unsaved LocationPoints to DatabaseManager.saveToDb() method?
How to catch internet connection or server "up" state? Is it a good idea to create some Observable, which will ping my remote server, and as result should emit some boolean value to it's subscribers? What is the best way to implement this behavior?
Is there a simple way to enqueue Retrofit calls with a locally saved data (from local DB), when internet connection (server) will become "up"?
How not to loose any of my LocationPoints on the server-side? (finally my client app must send all of them!
Am I doing something wrong? I am a newbie to Android, Java and
particularly to RxJava...
Interesting task! First of all: you don't need to create DB for storing such tiny info. Android has good place for storing any Serializable data.
So for saving locally data crate Model like:
public class MyLocation implements Serializable {
#Nonnull
private final String id;
private final Location location;
private final boolean isSynced;
// constructor...
// getters...
}
Singleton class:
public class UserPreferences {
private static final String LOCATIONS = "locations";
#Nonnull
private final SharedPreferences preferences;
#Nonnull
private final Gson gson;
private final PublishSubject<Object> locationRefresh = PublishSubject.create();
public void addLocation(MyLocation location) {
final String json = preferences.getString(LOCATIONS, null);
final Type type = new TypeToken<List<MyLocation>>() {
}.getType();
final List<MyLocation> list;
if (!Strings.isNullOrEmpty(json)) {
list = gson.fromJson(json, type);
} else {
list = new ArrayList<MyLocation>();
}
list.add(lication);
final String newJson = gson.toJson(set);
preferences.edit().putString(LOCATIONS, newJson).commit();
locationRefresh.onNext(null);
}
private List<String> getLocations() {
final String json = preferences.getString(LOCATIONS, null);
final Type type = new TypeToken<List<MyLocation>>() {
}.getType();
final List<MyLocation> list = new ArrayList<MyLocation>();
if (!Strings.isNullOrEmpty(json)) {
list.addAll(gson.<List<MyLocation>>fromJson(json, type));
}
return list;
}
#Nonnull
public Observable<List<MyLocation>> getLocationsObservable() {
return Observable
.defer(new Func0<Observable<List<MyLocation>>>() {
#Override
public Observable<List<MyLocation>> call() {
return Observable.just(getLocations())
.filter(Functions1.isNotNull());
}
})
.compose(MoreOperators.<List<MyLocation>>refresh(locationRefresh));
}
// also You need to create getLocationsObservable() and getLocations() methods but only for not synced Locations.
}
Change:
public interface GpsService {
#POST("/v1/savelocationbatch")
Observable<SaveResponse> saveLocationBatch(#Body LocationBatch locationBatch);
}
Now the most interesting...make it all works.
There is extention for RxJava. It has a lot of "cool tools" (btw, MoreOperators in UserPref class from there), also it has something for handling retrofit errors.
So let assume that location saving suppose to happens, when Observable saveLocationObservable emit something. In that case your code looks like:
final Observable<ResponseOrError<SaveResponse>> responseOrErrorObservable = saveLocationObservable
.flatMap(new Func1<MyLocation, Observable<ResponseOrError<SaveResponse>>>() {
#Override
public Observable<ResponseOrError<SaveResponse>> call(MyLocation myLocation) {
final LocationBatch locationBatch = LocationBatch.fromMyLocation(myLocation); // some method to convert local location to requesr one
return saveLocationBatch(locationBatch)
.observeOn(uiScheduler)
.subscribeOn(networkScheduler)
.compose(ResponseOrError.<SaveResponse>toResponseOrErrorObservable());
}
})
.replay(1)
.refCount();
final Observable<Throwable> error = responseOrErrorObservable
.compose(ResponseOrError.<SaveResponse>onlyError())
.withLatestFrom(saveLocationObservable, Functions2.<MyLocation>secondParam())
.subscribe(new Action1<MyLocation>() {
#Override
public void call(MyLocation myLocation) {
// save location to UserPref with flag isSynced=flase
}
});
final Observable<UserInfoResponse> success = responseOrErrorObservable
.compose(ResponseOrError.<SaveResponse>onlySuccess())
.subscribe(new Action1<SaveResponse>() {
#Override
public void call(SaveResponse response) {
// save location to UserPref with flag isSynced=true
}
});

Pass String to Activity

I am new to Android development and Java and was wondering if somebody could help me with the following:
I have created an application that runs a server thread listening on a specified port. I would like to print messages received from a connected client into a TextView in the activity.
The server thread is in a separate class. The run method in this class listens for a client connection and reads any data received into a String.
What would be the best way for me to transfer the contents of this String back to the activity so that it can update the TextView?
From my (limited) understanding, only the ui thread should update a TextView and I can't find a way to get runOnUiThread to update the TextView.
Added code as requested.
Activity code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView messages = (TextView) findViewById(R.id.messages);
try {
newThread server = new newThread(this, messages);
} catch(Exception e) {
Toast.makeText(ChatActivity.this, e.toString(), Toast.LENGTH_LONG).show();
}
}
Run method in newThread class:
public void run()
{
serv = new ServerSocket(8000);
while(true)
{
cli = serv.accept();
user = cli.getInetAddress().toString();
BufferedReader cli_in = new BufferedReader(new InputStreamReader(cli.getInputStream()));
OutputStreamWriter cli_out = new OutputStreamWriter(cli.getOutputStream());
while((buf = cli_in.readLine()) != null)
{
// Update the messages TextView with buf
}
}
}
To avoid making things too cluttered I have omitted what irrelevant code I can.
Basically, in the inner while loop in run() I would like to pass the "buf" String to the activity so that the messages textview can be updated with it's content.
Cheers
Maybe a bad idea, but how about using AsyncTask? Didn't try if this would work, but it just might, since onProgressUpdate has access to UI thread.
private TextView messages;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
messages = (TextView) findViewById(R.id.messages);
ReceiveTask receive = new ReceiveTask();
receive.execute(100)
}
private void updateTextView(String text)
{
messages.setText(text);
}
private class ReceiveTask extends AsyncTask<Integer, String, Long> {
#Override
protected void onPreExecute() {
}
protected Long doInBackground(Integer... urls) {
newThread nt = new newThread();
while(true)
{
publishProgress(run());
}
return (long)0;
}
protected void onProgressUpdate(String... value) {
updateTextView(value[0]); //method in Activity class, to update TextView
}
protected void onPostExecute(Long result) {
}
}
Basically publishProgress will send data to onProgressUpdate, which will then send data to method (updateTextView) in main class and update TextView.
Usually it helps if you tell people you're working on a chat. Also, run() will need to be modified, to return string back, and remove while(true) loop from it. This is NOT the best idea, I suggest you go through a few tutorials on how to make an android chat first.

AsyncTask Android - Design Pattern and Return Values

I'm writing an application that validates login credentials on an external webserver - so I have the basic issue of creating a login screen that when submitted will send an HTTP request to a server in the background and not cause the UI to hang - whilst providing a ProgressDialog to the user.
My problem lies in, I want to write a generic HTTP Request class that extends AsyncTask, so when I call .execute() I will then pass String parameters which may contain something like 'post', and when doInBackground is called this will see the 'post' string and then forward those parameters onto the respective call in my class. Pseudo code would be something like
public class HTTPOperations extends AsyncTask<String, Void, String>
{
doInBackground(String... string1,additionalParams)
{
if string1.equals "post"
response = httpPost(additionalParams)
return response;
}
httpPost(params)
{
// do http post request
}
}
This is all I could think of, other than creating a class for every HTTP Post/GET etc request I wish to make and extending ASyncTask...
Which leads me to my next problem, if the HTTP POST is successful and it returns an authentication token, how do I access this token?
Because new httpOperations.execute(), does not return the string from doInBackground, but a value of type
Sorry if this doesn't make sense, I can't figure this out at all. Please ask for elaboration if you need it. AsyncTask design patterns and ideas are hugely welcomed.
If you are designing a reusable task for something like this, you need to identify a reusable return type. Its a design decision on your part. Ask yourself, "Are my HTTP operations similar in both the mechanisms with which they are called and in which their data is processed?" If so, you can design a single class to do both. If not, you probably need different classes for your different remote operations.
In my personal use, I have an object i attach key value pairs to and the common return type is the HttpEntity. This is the return type for both HTTP Get and Post, and this seems to work ok in my scenarios because i throw exceptions in exceptional HTTP result situations, like 404. Another nice aspect of this setup is that the code to attach parameters to a get or post are fairly similar, so this logic is pretty easy to construct.
An example would be something like this (psuedo):
public interface DownloadCallback {
void onSuccess(String downloadedString);
void onFailure(Exception exception);
}
Then in your code, where you go to do the download:
DownloadCallback dc = new DownloadCallback(){
public void onSuccess(String downloadedString){
Log.d("TEST", "Downloaded the string: "+ downloadedString);
}
public void onFailure(Exception e){
Log.d("TEST", "Download had a serious failure: "+ e.getMessage());
}
}
DownloadAsyncTask dlTask = new DownloadAsyncTask(dc);
Then inside the constructor of DownloadAsyncTask, store the DownloadCallback and, when the download is complete or fails, call the method on the download callback that corresponds to the event. So...
public class DownloadAsyncTask extends AsyncTask <X, Y, Z>(){
DownloadCallback dc = null;
DownloadAsyncTask(DownloadCallback dc){
this.dc = dc;
}
... other stuff ...
protected void onPostExecute(String string){
dc.onSuccess(string);
}
}
I'm going to reiterate that I think for the good of yourself, you should pass back HttpEntities. String may seem like a good idea now, but it really leads to trouble later when you want to do more sophisticated logic behind your http calls. Of course, thats up to you. Hopefully this helps.
suppose the data format with web api is json, my design pattern :
common classes
1.MyAsyncTask : extends AsyncTask
2.BackgroundBase : parameters to server
3.API_Base : parameters from server
4.MyTaskCompleted : callback interface
public class MyAsyncTask<BackgroundClass extends BackgroundBase,APIClass extends API_Base> extends AsyncTask<BackgroundClass, Void, APIClass> {
private ProgressDialog pd ;
private MyTaskCompleted listener;
private Context cxt;
private Class<APIClass> resultType;
private String url;
private int requestCode;
public MyAsyncTask(MyTaskCompleted listener, Class<APIClass> resultType, int requestCode, String url){
this.listener = listener;
this.cxt = (Context)listener;
this.requestCode = requestCode;
this.resultType = resultType;
this.url = url;
}
public MyAsyncTask(MyTaskCompleted listener, Class<APIClass> resultType, int requestCode, String url, ProgressDialog pd){
this(listener, resultType, requestCode, url);
this.pd = pd;
this.pd.show();
}
#Override
protected APIClass doInBackground(BackgroundClass... params) {
APIClass result = null;
try {
//do something with url and params, and get data from WebServer api
BackgroundClass oParams = params[0];
String sUrl = url + "?d=" + URLEncoder.encode(oParams.getJSON(), "UTF-8");
String source = "{\"RtnCode\":1, \"ResultA\":\"result aaa\", \"ResultB\":\"result bbb\"}";
//to see progressdialog
Thread.sleep(2000);
result = new com.google.gson.Gson().fromJson(source, resultType);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
#Override
protected void onPostExecute(APIClass result) {
super.onPostExecute(result);
try {
if(pd != null && pd.isShowing())
pd.dismiss();
API_Base oApi_Base = (API_Base)result;
listener.onMyTaskCompleted(result , this.requestCode);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class API_Base {
public int RtnCode;
public String getJSON(Context context) throws Exception
{
return new com.google.gson.Gson().toJson(this);
}
public String toString(){
StringBuilder sb = new StringBuilder();
for (Field field : this.getClass().getFields()) {
try {
field.setAccessible(true);
Object value = field.get(this);
if (value != null) {
sb.append(String.format("%s = %s\n", field.getName(), value));
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
return sb.toString();
}
}
public class BackgroundBase {
public String getJSON() throws Exception
{
return new com.google.gson.Gson().toJson(this);
}
}
public interface MyTaskCompleted {
void onMyTaskCompleted(API_Base oApi_Base, int requestCode) ;
}
example, let's call two api in one activity
assume :
API 1.http://www.google.com/action/a
input params : ActionA
output params : RtnCode, ResultA
API 2.http://www.google.com/action/b
input params : ActionB
output params : RtnCode, ResultB
classes with example :
1.MyActivity : extends Activity and implements MyTaskCompleted
2.MyConfig : utility class, i set requestCode here
3.BackgroundActionA, BackgroundActionB : model classes for api's input params
4.API_ActionA, API_ActionB : model classes for api's output params
public class MyActivity extends Activity implements MyTaskCompleted {
ProgressDialog pd;
Button btnActionA, btnActionB;
TextView txtResult;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout);
btnActionA = (Button)findViewById(R.id.btn_actionA);
btnActionB = (Button)findViewById(R.id.btn_actionB);
txtResult = (TextView)findViewById(R.id.txt_result);
btnActionA.setOnClickListener(listener_ActionA);
btnActionB.setOnClickListener(listener_ActionB);
pd = new ProgressDialog(MyActivity.this);
pd.setTitle("Title");
pd.setMessage("Loading");
}
Button.OnClickListener listener_ActionA = new Button.OnClickListener(){
#Override
public void onClick(View v) {
//without ProgressDialog
BackgroundActionA oBackgroundActionA = new BackgroundActionA("AAA");
new MyAsyncTask<BackgroundActionA, API_ActionA>(MyActivity.this,
API_ActionA.class,
MyConfig.RequestCode_actionA,
"http://www.google.com/action/a").execute(oBackgroundActionA);
}
};
Button.OnClickListener listener_ActionB = new Button.OnClickListener(){
#Override
public void onClick(View v) {
//has ProgressDialog
BackgroundActionB oBackgroundActionB = new BackgroundActionB("BBB");
new MyAsyncTask<BackgroundActionB, API_ActionB>(MyActivity.this,
API_ActionB.class,
MyConfig.RequestCode_actionB,
"http://www.google.com/action/b",
MyActivity.this.pd).execute(oBackgroundActionB);
}
};
#Override
public void onMyTaskCompleted(API_Base oApi_Base, int requestCode) {
// TODO Auto-generated method stub
if(requestCode == MyConfig.RequestCode_actionA){
API_ActionA oAPI_ActionA = (API_ActionA)oApi_Base;
txtResult.setText(oAPI_ActionA.toString());
}else if(requestCode == MyConfig.RequestCode_actionB){
API_ActionB oAPI_ActionB = (API_ActionB)oApi_Base;
txtResult.setText(oAPI_ActionB.toString());
}
}
}
public class MyConfig {
public static String LogTag = "henrytest";
public static int RequestCode_actionA = 1001;
public static int RequestCode_actionB = 1002;
}
public class BackgroundActionA extends BackgroundBase {
public String ActionA ;
public BackgroundActionA(String actionA){
this.ActionA = actionA;
}
}
public class BackgroundActionB extends BackgroundBase {
public String ActionB;
public BackgroundActionB(String actionB){
this.ActionB = actionB;
}
}
public class API_ActionA extends API_Base {
public String ResultA;
}
public class API_ActionB extends API_Base {
public String ResultB;
}
Advantage with this design pattern :
1.one Advantage for multi api
2.just add model classes for new api, ex: BackgroundActionA and API_ActionA
3.determine which API by different requestCode in callback function : onMyTaskCompleted

Categories

Resources