I have got the same problem for 8 days now and i hope that someone can help me, i am working on an application that parse multiple RSS feeds and display them on listview with fragment but the problem is that the UI get blocked for 2 to 4 seconds, and every time the user touches the screen the application crashes.
I tracked the problem and i found out that i had to bring the data asynchronously, which i actually did, but every time i have a fragment, the same problem occurs.
In fact,the fragment blocks when it brings data if it does it from a main thread, while i actually bring data asynchronously, that's why i can't get the reason for the blocking.
This is how i change my code, this is my interface
public interface OnTaskFinishedListener {
void onTaskFinished(ArrayList<Article> articles);
}
i am getting the ArrayList in the AsyncTask like this
public class AndroidSaxFeedParserAsync extends AsyncTask<String, Long,ArrayList<Article>> {
ArrayList<Article> rssItems = new ArrayList<Article>();
public URL feedUrl;
OnTaskFinishedListener onTaskFinishedListener;
public AndroidSaxFeedParserAsync(OnTaskFinishedListener _onTaskFinishedListener)
{
onTaskFinishedListener = _onTaskFinishedListener ;
}
#Override
protected void onPostExecute(ArrayList<Article> result) {
super.onPostExecute(result);
onTaskFinishedListener.onTaskFinished(result);
}
#Override
protected ArrayList<Article> doInBackground(String... params) {
//....
return rssItems;
}
}
and finally i get it here in my fragment like this
public class FeedPlayerIndexFragment extends SherlockFragment implements OnTaskFinishedListener{
String url="http://www.whatever.com/index.php?format=feed&type=rss";
ArrayList<Article> feeds;
ItemAdapterArticle lfa;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
new AndroidSaxFeedParserAsync(new OnTaskFinishedListener() {
#Override
public void onTaskFinished(ArrayList<Article> articles) {
}
}).execute(url);
View view=inflater.inflate(R.layout.main, container, false);
//....
return view;
}
#Override
public void onTaskFinished(ArrayList<Article> articles) {
lfa = new ItemAdapterArticle(getActivity().getApplicationContext(), articles);
}
}
Problem is that you're using AsyncTask.get() call which is actually blocking. There's another solution on this problem which doesn't block the UI thread. You need to define a simple interface:
public interface OnTaskFinishedListener {
void onTaskFinished(List<Article> articles);
}
The calling Fragment should implement this interface. When instantiating the AsyncTask you send your Fragment as an OnTaskFinishedListener instance as a parameter to constructor. The AsyncTask should hold the reference to OnTaskFinishedListener instance, and when the work is done, in onPostExecute(), you call onTaskFinished() on the instance. Now, in the Fragment you can respond to this call and use the articles parameter to populate your UI components. Hope this helps.
Related
I created the instance of View Model in onCreate method of an activity.
ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
Then i have a method, AddTicket, which uses viewModel to hit a service and on response from viewModel i dismiss loading animation.
public void addTicket(View view){
ticketViewModel.AddTicket(id).observe(this, response ->{
dismissLoadingAnimation();
}
Now after adding a ticket, user can repress the Add Ticket button, and the addTicket() method will be called again.
but this time observer defined in ViewModel gets called 2 times, resulting in 2 network calls, and 2 dismissLoadingAnimation execution.
And if i keep pressing addTicket button, the number of executing observer defined inside ViewModel keep increases.
This is my View Model code.
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();
public MutableLiveData AddTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
return mObservableResponse;
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
mObservableResponse.setValue("success");
}
}
}
The number of executing observer defined inside ViewModel keep increases becasue with every click You're registering new observers. You're not supposed to register observer with onClick() method.
You should do it in onCreate() method of your Activity or in onViewCreated method of your fragment. If You'll do that, there won't be a need to removeObserver when You'll finish work. Lifecycle mechanism will cover it for you.
But if you really want answer for you question, this is how you can do it
yourViewModel.yourList.removeObservers(this)
Passing this means passing your Activity, or there is a second way:
yourViewModel.yourList.removeObserver(observer)
val observer = object : Observer<YourObject> {
override fun onChanged(t: YourObject?) {
//todo
}
}
The purpose of Viewmodel is to expose observables (Livedata)
The purpose of View(Activity/Fragment) is to get these observables and observe them
Whenever there is a change in these observables(Livedata) the change is automatically posted to the active subscribed owners(Activity/Fragment), so you need not remove them in onPause/onStop as it is not mandatory
I can suggest few changes to your code to solve the problem with the above mentioned pointers
ViewModel
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
MutableLiveData<String> mObservableResponse = new MutableLiveData<String>();
public LiveData<String> getResponseLiveData(){
return mObservableResponse;
}
public void AddTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
mObservableResponse.setValue("success");
}
}
}
View
onCreate(){
ticketViewModel = ViewModelProviders.of(this).get(TicketViewModel.class);
observeForResponse();
}
private void observeForResponse(){
ticketViewModel.getResponseLiveData().observe(this, response ->{
//do what has to be updated in UI
}
}
public void addTicket(View view){
ticketViewModel.AddTicket(id);
}
Hope this is of help :)
You only need to call the observe once, I prefer to do it in onResume and then call removeObserver in onPause:
Adds the given observer to the observers list
You keep adding listeners to the data so you get multiple callbacks.
Edit:
I took an existing code sample of mine for a Fragment and renamed everything (I hope), there's no example here for setting the data into the ViewModel but it should be ticketViewModel.AddTicket(id); in your case.
public class ListFragment extends Fragment {
private MyViewModel viewModel;
private MyRecyclerViewAdapter recyclerViewAdapter;
private Observer<List<DatabaseObject>> dataObserver;
private RecyclerView recyclerView;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_layout, container, false);
initRecyclerView(rootView, getContext());
initObservers();
return rootView;
}
private void initRecyclerView(View rootView, Context context) {
recyclerViewAdapter = new MyRecyclerViewAdapter(context);
recyclerView = rootView.findViewById(R.id.recycler_view);
recyclerView.setAdapter(recyclerViewAdapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.addItemDecoration(new DividerNoLastItemDecoration());
}
private void initObservers() {
dataObserver = new Observer<List<DatabaseObject>>() {
#Override
public void onChanged(#Nullable final List<DatabaseObject> data) {
recyclerViewAdapter.setData(data);
}
};
}
#Override
public void onResume() {
super.onResume();
initViewModel();
}
private void initViewModel() {
FragmentActivity activity = getActivity();
if (activity != null) {
viewModel = ViewModelProviders.of(activity).get(MyViewModel.class);
viewModel.getData().observe(activity, dataObserver);
}
}
#Override
public void onPause() {
super.onPause();
if (viewModel != null) {
viewModel.getData().removeObserver(dataObserver);
viewModel = null;
}
}
}
I had similar problem. You could try to use SingleLiveEvent
Or, in my, more complicated case, i had to use custom observer. It would looks like this:
public class CustomObserver implements Observer<YourType> {
private MyViewModel mViewModel;
public CustomObserver (){}
public void setViewModel(MyViewModel model) {
mViewModel = model;
}
#Override
public void onChanged(#Nullable YourType object) {
mViewModel.AddTicket(id).removeObserver(this); // removing previous
mmViewModel.refreshTickets(); // refreshing Data/UI
// ... do the job here
// in your case it`s: dismissLoadingAnimation();
}
}
And using it like:
public void addTicket(View view){
ticketViewModel.AddTicket(id).observe(this, myCustomObserver);
}
If you are willing to do some changes, i think we can handle it in much cleaner way
LiveData is meant to be used to contain a property value of a view
In ViewModel
public class TicketViewModel extends AndroidViewModel implements IServiceResponse {
private MutableLiveData<Boolean> showLoadingAnimationLiveData = new MutableLiveData<String>();
public LiveData<Boolean> getShowLoadingAnimationLiveData(){
return showLoadingAnimationLiveData;
}
public void addTicket(String id){
JsonObject jsonObject= new JsonObject();
jsonObject.addProperty("id", id);
NetworkUtility networkUtility= new NetworkUtility(this, ADD_TICKET);
networkUtility.hitService(URL, jsonObject, RequestMethods.POST);
showLoadingAnimationLiveData.setValue(true);
}
#Override
public void onServiceResponse(String response, String callType){
if(serviceTag.equalsIgnoreCase(ADD_TICKET)){
showLoadingAnimationLiveData.setValue(false);
}
}
}
In 'onCreate' of your Activity/Fragment
ticketViewModel.getShowLoadingAnimationLiveData().observe(this,showLoadingAnimation->{
if(showLoadingAnimation != null && showLoadingAnimation){
startLoadingAnimation();
}else{
dismissLoadingAnimation();
}
})
The main concept is to divide the responsibilities,
Activity/Fragment doesn't need to know which process is going on, they only need to know what are the current properties/state of there child views.
We need to maintain a LiveData in ViewModels for each changing property/state depending on Views. ViewModel needs to handle the view states depending on whats happening.
Only responsibility the Activity/Fragment has about a process is to trigger it and forget and ViewModel needs handle everything(like informing Repositories to do the work and changing View Properties).
In your Case,
'addTicket' is a process about which Activity/Fragment doesn't need to know about there status.
The only responsibility of Activity/Fragment about that process is to trigger it.
ViewModel is one who needs to analyze the state of process(in-progress/success/failed) and give appropriate values to the LiveDatas to inform the respective Views
I am working on an Android app. The code I attach is creating a recyclerview. The very first thing we do is to create an asynctask that would fetch data on an SQLite database and load it into the adapter->recylcerview. While the background task is working, a progressdialog is shown to the user.
public class HomeActivity extends AppCompatActivity
{
private RecyclerView recycler;
private RecyclerViewAdapter adapter;
private SwipeRefreshLayout swipeRefresh;
private progressDialog progressDialog;
// ... some code here
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... some code here
createRecyclerView();
loadRecyclerView();
// ... some code here
}
private void loadRecyclerView()
{
new LoadingBackgroundTask().execute();
}
private void createRecyclerView()
{
Context context = getApplicationContext();
recycler = (RecyclerView) findViewById(R.id.recycle_view_home);
recycler.setHasFixedSize(true);
RecyclerView.LayoutManager lManager = new LinearLayoutManager(context);
recycler.setLayoutManager(lManager);
adapter = new RecyclerViewAdapter();
recycler.setAdapter(adapter);
recycler.setItemAnimator(new DefaultItemAnimator());
}
private class LoadingBackgroundTask extends AsyncTask<Void, Void, List<items>> {
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = ProgressDialog.show(HomeActivity.this, getString(R.string.dialog_load_list),getString(R.string.dialog_please_wait), false, false);
}
#Override
protected List doInBackground(Void... params) {
List<items> lists;
//Data Source Class ( SQLite)
ListDS listDS = new ListDS(getApplicationContext());
list = listDS.getList();
return list;
}
#Override
protected void onPostExecute(List result) {
super.onPostExecute(result);
//it inserts de list on recyclerview performing animation
adapter.animate(result);
progressDialog.dissmiss();
swipeRefresh.setRefreshing(false);
recycler.scrollToPosition(0);
}
}
}
So far, so good. However, as you probably know this code has some well-known issues; for example if I rotate the screen while asynctask is doing its magic, it will crash the app.
I've tried an alternative I've seen Googling, rxandroid.
(Sorry if I typed something wrong, I am doing it by memory)
public class HomeActivity extends AppCompatActivity
{
private Subscriber suscriptor;
private progressDialog progressDialog;
//some code ....
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
suscriptor = new Subscriber() {
#Override
public void onCompleted() {
progressDialog.dismiss();
Log.d("SUSCRIPTOR","ON COMPLETE");
}
#Override
public void onError(Throwable e) {
Log.d("SUSCRIPTOR","ON ERROR");
}
#Override
public void onNext(Object o) {
adapter.animate((List<items>)o);
}
};
Observable.create(
new Observable.OnSubscribe<List<items>>() {
#Override
public void call(Subscriber<? super List<items>> sub) {
progressDialog = ProgressDialog.show(HomeActivity.this, getString(R.string.dialog_load_list),getString(R.string.dialog_please_wait), false, false);
List<items> lists;
//Data Source Class ( SQLite)
ListDS listDS = new ListDS(getApplicationContext());
list = listDS.getList();
sub.onNext(list);
sub.onCompleted();
}
#Override
protected void finalize() throws Throwable {
super.finalize();
Log.d("OBSERAVBLE","FINALIZED");
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.newThread())
.cache()
.subscribe(suscriptor);
}
#Override
public void onDestroy()
{
if(suscriptor!=null)
{
if(!suscriptor.isUnsubscribed())
{
suscriptor.unsubscribe();
}
}
super.onDestroy();
}
}
Now the app is not crashing anymore when I rotate the screen. However, the observable keeps working on the background until it finishes but as I unsubscribe to avoid crashing, I don't receive the results properly. Moreover, progressbar disappears even though the observable keeps working as I mentioned.
Looking for a solution, I found there is a pattern called "Ted Mosby" which seems to solve my problem. Although it looks promising, I think is too much coding for something I felt it is not worth it and that rxandroid may have a workaround.
So, my question is how can I get what I want without getting immersed in an architectural coding mess too big for my purpose? Could you give an example if you guys have solved this? Do you think I am wrong and I should implement TedMosby pattern?
Mosby is a Model-View-Presenter (MVP) library. So the pattern you named "ted mosby pattern" is actually MVP.
But you didn't have understood what MVP is all about. It's not about retaining async running taks, even thought this could be achieved with Mosby. MVP is about separation of concerns. View is just displaying UI elements, Presenter is controlling the View, i.e. the presenter tells the view: now display the progress diaolog, now hide the progress dialog, etc. In other words, the presenter controls the state of the view. The Model could be an async task or a RxJava Observable. The Presenter than gets the result back and tell the view to display it. You code is decoupled into 3 layers Model (also called business logic) and Presenter and View. The advantage is that you can change the view (i.e. replace progress dialog with an progressbar widget) without touching any code that loads data (Presenter and business logic). Additionally with MVP your code becomes testable.
So what you should compare is: Should I use AsyncTask or RxJava for loading data. With Mosby you would execute your http request in the presenter. While orientation changes are done the presenter doesn't get destroyed (and hence the background task doesn't get canceled).
However, MVP is not the solution for everything. If you have to ensure that a single http call is executed correctly (i.e. sign up for a community) you should think about using an android service.
What you can do is whatever you do in activity like your AsycTask and RecyclerView, put it inside a fragment and, setRetainInstance(true) in onCreateView() method of fragment and load that fragment in your activity.
setRetainInstance(true) won't let your fragment instance destroy when screen is rotated.
Your Observable should handle himself the fact on unsubscription. There are two mechanism for this:
check subscriber.isUnsubscribed. You can do it between or after "heavy" steps
add unsubscription callback. You can use it to stop long running operations, release resources etc
Take a look at this code:
Observable.create(
new Observable.OnSubscribe<List<items>>() {
#Override
public void call(Subscriber<? super List<items>> sub) {
sub.add(Subscriptions.create(new Action0() {
#Override
public void call() {
cancelLongRunningOperationIfItStillRunning();
}
}));
if (!sub.isUnsubscribed()) {
//start long running operation here
}
}
})
.doOnSubscribe(new Action0() {
#Override
public void call() {
}
})
You shouldn't reference to your activity/context/progress dialog etc inside Observable. Instead use doOnSubscribe if you want to do some side effects.
I was looking over this answer and it seemed to only deal with a single textview.
Basically, I have an Android application with n fragments, each of which has a textview that is populated from a remote call to a database. Each time the fragment is selected, that remote call will fire and the textview should be repopulated.
Currently, I am using a central AsyncTask to accomplish this, however I am starting to wonder if it is the correct way to go about doing so (some textviews take too long to update for small amounts of data, some don't get updated at all, etc.).
Here is the code from my RetrieveData class. Essentially, it figures out which textview is to be updated, and then populates that textview.
public class RetrieveData extends AsyncTask<String, String, String[]> {
private int txtViewID = -1;
private Activity mainActivity;
public RetrieveData(Activity a) { mainActivity = a; }
protected String[] doInBackground(String... urls) {
String[] data;
// call web script to return JSON data
...
// figure out which fragment called which script
if (urls[0] == "get_A.php") {
data = parseJSONdata(); // parse out the JSON
txtViewID = R.id.txtViewA; // find INT-based ID
} else if (urls[0] == "get_B.php") {
data = parseOtherJSONdata(); // different type of call
txtViewID = R.id.txtViewB;
} else ... {
...
}
} catch (Exception e) {
System.out.println("Error: " + e.toString());
}
return data;
}
#Override
protected void onPostExecute(String[] op) {
if (txtViewID != -1) { // call was made
TextView tv = (TextView)mainActivity.findViewById(txtViewID);
tv.setText(op[0]);
}
and here is how I call this from a Fragment:
public class MainFragment extends Fragment {
Activity mainActivity;
public MainFragment(Activity a) { mainActivity = a; }
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.main_tab,container,false);
new RetrieveData(mainActivity).execute("get_A.php","1");
return v;
}
}
To me, its very kludgy and probably belies my newness to Android, so any suggestions for improvement are heartily appreciated.
You can do a couple of things to improve the robustness and performance and fix some issues which will creep in later:
Don't use findViewById() outside of init/setup type methods. It is an expensive call as it has to "search" your hierarchy for the ID you are requesting.
Don't use an overloaded constructor for your Fragment which takes the Activity. The Fragment default constructor should be empty. This allows the system to properly re-create your Fragment when configuration changes (screen rotates.) The Fragment will receive its attached Activity at the correct time when its onAttach() method is called, so there is no need to do this.
You shouldn't need the Activity at all for what you're trying to do. Instead, have your Fragment get the correct TextView from your layout in its onCreateView(). What you do from there is really up to you:
Pass the TextView instance to your RetrieveData class constructor as the one to be updated. This eliminates the hard coded IDs in your RetrieveData class, which gets rid of some explicit coupling and is a better approach. This is still very tightly coupled, though, since it depends on having a specific View so still not a great option IMHO.
Have the RetrieveData class define an inner Callback interface and have the Fragment implement it. The constructor for RetrieveData can then take an instance of the Callback interface (e.g. your Fragment instance) and when its onPostExecute() runs it just calls back the Fragment with the appropriate data. Now it is up to your Fragment implementation to make the right decision on what UI element it is hosting to update with the data. It may be a TextView now, but in the future you could make it something else, etc. Now you have decoupled the class from all explicit UI ties and put the responsibility on the thing hosting the UI elements: the Fragment.
Here's a brief example of the 2nd bullet:
public RetrieveData extends AsyncTask<String, String, String[]> {
// Define the interface used to provide results
public interface Callback {
public void onDataLoaded(String[] result);
}
private Callback mCb;
public RetrieveData(Callback cb) {
mCb = cb;
}
...
#Override
public void onPostExecute(String[] result) {
mCb.onDataLoaded(result);
}
}
public MyFragment extends Fragment implements RetrieveData.Callback {
TextView mResult;
RetrieveData mAsyncRetriever;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.main_tab,container,false);
// Get the TextView now where we want to show results.
// This avoids calling findViewById() constantly.
mResult = (TextView)root.findViewById(R.id.example_result);
...
}
#Override
public void onResume() {
// Keep a reference to the AsyncTask so we can properly
// cancel it when our lifecycle events dictate so.
mAsyncRetriever = new RetrieveData(this);
mAsyncRetriever.execute("get_A.php");
}
#Override
public void onPause() {
// If we have a pending data load going on, kill it.
if (mAsyncRetriever != null) {
mAsyncRetriever.cancel(true);
mAsyncRetriever = null;
}
}
#Override
public void onDataLoaded(String[] result) {
// Only pulling the first result provided
mResult.setText(result[0]);
// The RetrieveData is done, get rid of our ref
mAsyncRetriever = null;
}
}
I'm using SQLite database in android and want to listening for any database changes. How can I do this?
Thanks for all future help!
In fact, SQLite offers such functionality: SQLite Data Change Notification Callbacks
How it can be used in Android is another story though..
SQLite doesn't offer any change listener functionality; you have to monitor it yourself. The simplest way to achieve this would be to send a Broadcast (or even better, a LocalBroadcast) anytime you modify the database. Some of the database libraries already offer functionality that is similar to this - check out GreenDAO.
a simple implementation of changeListener for the database on Android
suppose that you have a class to handle your queries in your android app, we need to make the database methods observable.
and also we need some listeners to observe the abovementioned observable. let's make the database handler observable:
let's make the observable interface:
public interface DatabaseObservable {
//register the observer with this method
void registerDbObserver(DatabaseObserver databaseObserver);
//unregister the observer with this method
void removeDbObserver(DatabaseObserver databaseObserver);
//call this method upon database change
void notifyDbChanged();
}
now implement the observable in your database class
public class LocalStorageDb extends SQLiteOpenHelper implements DatabaseObservable {
LocalStorageDb lDb;
//make it Singleton
public static synchronized LocalStorageDB getInstance(Context context) {
if (mlLocalQuickChatDB == null) {
mlLocalQuickChatDB = new LocalStorageDB(context.getApplicationContext());
}
return mlLocalQuickChatDB;
}
//there are some methods to do some queries
public void createContact(Foo foo, Bar bar){
//some queries here
//call the Observable Method to let know the observers that it has changed
onDatabaseChanged();
}
//now override the DatabaseObservable method which is responsible to notify the listeners
#Override
public void onDatabaseChanged() {
for (DatabaseObserver databaseObserver:observerArrayList){
if (databaseObserver!= null){
databaseObserver.onDatabaseChanged();
}}
}
//also you need functions to **register** or **unregister** the observers:
#Override
public void registerDbObserver(DatabaseObserver databaseObserver) {
if (!observerArrayList.contains(databaseObserver)){
observerArrayList.add(databaseObserver);
}
#Override
public void removeDbObserver(DatabaseObserver databaseObserver) {
observerArrayList.remove(databaseObserver);
}
then we need an observer to observe the changes:
public interface DatabaseObserver {
void onDatabaseChanged();
}
now in your activity or fragment, there is a function to fetch the changes, like getLocalContact. implement the observer on the fragment for example:
public class ExampleFragment extends Fragment implements DatabaseObserver {
LocalStorageDB localStorageDB;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
localStorageDB = LocalStorageDB.getInstance();
}
#Override
public void onPause() {
super.onPause();
localStorageDB.removeObserver(this);
}
#Override
public void onResume() {
localStorageDB.registerObserver(this);
super.onResume();
}
public ExampleFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_example, container, false);
}
#Override
public void onDatabaseChanged() {
getLocalContact();
}
private void getLocalContact(){
//function to fetch contacts from database
}
}
Hi I'd like to add a new suggestion Incase of firebase usage. You can make a new json node for users messages and use On Data Change to detect the new number of unread messages then update your ui in the onDatachange. Is that smart or far away from the main idea?
This question already has answers here:
How to get the result of OnPostExecute() to main activity because AsyncTask is a separate class?
(17 answers)
Closed 6 years ago.
I have the following asynctask class which is not inside the activity. In the activity I'm initializing the asynctask, and I want the asynctask to report callbacks back to my activity.
Is it possible? Or does the asynctask must be in the same class file as the activity?
protected void onProgressUpdate(Integer... values)
{
super.onProgressUpdate(values);
caller.sometextfield.setText("bla");
}
Something like this?
You can create an interface, pass it to AsyncTask (in constructor), and then call method in onPostExecute()
For example:
Your interface:
public interface OnTaskCompleted{
void onTaskCompleted();
}
Your Activity:
public class YourActivity implements OnTaskCompleted{
// your Activity
}
And your AsyncTask:
public class YourTask extends AsyncTask<Object,Object,Object>{ //change Object to required type
private OnTaskCompleted listener;
public YourTask(OnTaskCompleted listener){
this.listener=listener;
}
// required methods
protected void onPostExecute(Object o){
// your stuff
listener.onTaskCompleted();
}
}
EDIT
Since this answer got quite popular, I want to add some things.
If you're a new to Android development, AsyncTask is a fast way to make things work without blocking UI thread. It does solves some problems indeed, there is nothing wrong with how the class works itself. However, it brings some implications, such as:
Possibility of memory leaks. If you keep reference to your Activity, it will stay in memory even after user left the screen (or rotated the device).
AsyncTask is not delivering result to Activity if Activity was already destroyed. You have to add extra code to manage all this stuff or do you operations twice.
Convoluted code which does everything in Activity
When you feel that you matured enough to move on with Android, take a look at this article which, I think, is a better way to go for developing your Android apps with asynchronous operations.
I felt the below approach is very easy.
I have declared an interface for callback
public interface AsyncResponse {
void processFinish(Object output);
}
Then created asynchronous Task for responding all type of parallel requests
public class MyAsyncTask extends AsyncTask<Object, Object, Object> {
public AsyncResponse delegate = null;//Call back interface
public MyAsyncTask(AsyncResponse asyncResponse) {
delegate = asyncResponse;//Assigning call back interfacethrough constructor
}
#Override
protected Object doInBackground(Object... params) {
//My Background tasks are written here
return {resutl Object}
}
#Override
protected void onPostExecute(Object result) {
delegate.processFinish(result);
}
}
Then Called the asynchronous task when clicking a button in activity Class.
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
Button mbtnPress = (Button) findViewById(R.id.btnPress);
mbtnPress.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MyAsyncTask asyncTask =new MyAsyncTask(new AsyncResponse() {
#Override
public void processFinish(Object output) {
Log.d("Response From Asynchronous task:", (String) output);
mbtnPress.setText((String) output);
}
});
asyncTask.execute(new Object[] { "Youe request to aynchronous task class is giving here.." });
}
});
}
}
Thanks
IN completion to above answers, you can also customize your fallbacks for each async call you do, so that each call to the generic ASYNC method will populate different data, depending on the onTaskDone stuff you put there.
Main.FragmentCallback FC= new Main.FragmentCallback(){
#Override
public void onTaskDone(String results) {
localText.setText(results); //example TextView
}
};
new API_CALL(this.getApplicationContext(), "GET",FC).execute("&Books=" + Main.Books + "&args=" + profile_id);
Remind: I used interface on the main activity thats where "Main" comes, like this:
public interface FragmentCallback {
public void onTaskDone(String results);
}
My API post execute looks like this:
#Override
protected void onPostExecute(String results) {
Log.i("TASK Result", results);
mFragmentCallback.onTaskDone(results);
}
The API constructor looks like this:
class API_CALL extends AsyncTask<String,Void,String> {
private Main.FragmentCallback mFragmentCallback;
private Context act;
private String method;
public API_CALL(Context ctx, String api_method,Main.FragmentCallback fragmentCallback) {
act=ctx;
method=api_method;
mFragmentCallback = fragmentCallback;
}
I will repeat what the others said, but will just try to make it simpler...
First, just create the Interface class
public interface PostTaskListener<K> {
// K is the type of the result object of the async task
void onPostTask(K result);
}
Second, create the AsyncTask (which can be an inner static class of your activity or fragment) that uses the Interface, by including a concrete class. In the example, the PostTaskListener is parameterized with String, which means it expects a String class as a result of the async task.
public static class LoadData extends AsyncTask<Void, Void, String> {
private PostTaskListener<String> postTaskListener;
protected LoadData(PostTaskListener<String> postTaskListener){
this.postTaskListener = postTaskListener;
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
if (result != null && postTaskListener != null)
postTaskListener.onPostTask(result);
}
}
Finally, the part where your combine your logic. In your activity / fragment, create the PostTaskListener and pass it to the async task. Here is an example:
...
PostTaskListener<String> postTaskListener = new PostTaskListener<String>() {
#Override
public void onPostTask(String result) {
//Your post execution task code
}
}
// Create the async task and pass it the post task listener.
new LoadData(postTaskListener);
Done!