I currently have an Android activity which manages some locally-stored RSS feeds. In this activity, these feeds are updated in their own thread via a private class. I'm also trying to include an "updating" icon that rotates with a RotateAnimation while this thread is running.
The animation works by itself, but doesn't work while the thread is running despite the log entries stating the code is being executed. I suspect this is due to the thread not being entirely safe, and taking up most of the CPU time. However I'd just like to know if there's a better way of achieving this.
The function updateAllFeeds() is called from a button press. Here's the relevant code:
/**
* Gets the animation properties for the rotation
*/
protected RotateAnimation getRotateAnimation() {
// Now animate it
Log.d("RSS Alarm", "Performing animation");
RotateAnimation anim = new RotateAnimation(359f, 0f, 16f, 21f);
anim.setInterpolator(new LinearInterpolator());
anim.setRepeatCount(Animation.INFINITE);
anim.setDuration(700);
return anim;
}
/**
* Animates the refresh icon with a rotate
*/
public void setUpdating() {
btnRefreshAll.startAnimation(getRotateAnimation());
}
/**
* Reverts the refresh icon back to a still image
*/
public void stopUpdating() {
Log.d("RSS Alarm", "Stopping animation");
btnRefreshAll.setAnimation(null);
refreshList();
}
/**
* Updates all RSS feeds in the list
*/
protected void updateAllFeeds() {
setUpdating();
Updater updater = new Updater(channels);
updater.run();
}
/**
* Class to update RSS feeds in a new thread
* #author Michael
*
*/
private class Updater implements Runnable {
// Mode flags
public static final int MODE_ONE = 0;
public static final int MODE_ALL = 1;
// Class vars
Channel channel;
ArrayList<Channel> channelList;
int mode;
/**
* Constructor for singular update
* #param channel
*/
public Updater(Channel channel) {
this.mode = MODE_ONE;
this.channel = channel;
}
/**
* Constructor for updating multiple feeds at once
* #param channelList The list of channels to be updated
*/
public Updater(ArrayList<Channel> channelList) {
this.mode = MODE_ALL;
this.channelList = channelList;
}
/**
* Performs all the good stuff
*/
public void run() {
// Flag for writing problems
boolean write_error = false;
// Check if we have a singular or list
if(this.mode == MODE_ONE) {
// Updating one feed only
int updateStatus = channel.update(getApplicationContext());
// Check for error
if(updateStatus == 2) {
// Error - show dialog
write_error = true;
}
}
else {
// Iterate through all feeds
for(int i = 0; i < this.channelList.size(); i++) {
// Update this item
int updateStatus = channelList.get(i).update(getApplicationContext());
if(updateStatus == 2) {
// Error - show dialog
write_error = true;
}
}
}
// If we have an error, show the dialog
if(write_error) {
runOnUiThread(new Runnable(){
public void run() {
showDialog(ERR_SD_READ_ONLY);
}
});
}
// End updater
stopUpdating();
} // End run()
} // End class Updater
(I know the updateStatus == 2 bit is bad practice, that's one of the next things I plan to tidy up).
Any help is greatly appreciated, many thanks in advance.
Run the updater runnable in a separate thread. Do the following changes.
protected void updateAllFeeds() {
setUpdating();
new Thread( new Updater(channels)).start();
}
Call stopUpdating() in runOnUiThread block.
private class Updater implements Runnable {
.........
.........
.........
public void run() {
.........
.........
// End updater
runOnUiThread(new Runnable(){
public void run() {
stopUpdating();
}
});
} // End run()
} // End class Updater
Move anything that affects the UI off into its own Runnable and then post it with your button
btnRefreshAll.post(new StopUpdating());
I managed to get this working last night using Android's AsyncTask class. It was surprisingly easy to implement, though the downside was I had to write one class for updating an individual feed, and another for updating all feeds. Here's the code for updating all feeds at once:
private class MassUpdater extends AsyncTask<ArrayList<Channel>, Void, Void> {
#Override
protected Void doInBackground(ArrayList<Channel>... channels) {
ArrayList<Channel> channelList = channels[0];
// Flag for writing problems
boolean write_error = false;
// Iterate through all feeds
for(int i = 0; i < channelList.size(); i++) {
// Update this item
int updateStatus = channelList.get(i).update(getApplicationContext());
if(updateStatus == FileHandler.STATUS_WRITE_ERROR) {
// Error - show dialog
write_error = true;
}
}
// If we have an error, show the dialog
if(write_error) {
runOnUiThread(new Runnable(){
public void run() {
showDialog(ERR_SD_READ_ONLY);
}
});
}
return null;
}
protected void onPreExecute() {
btnRefreshAll.setAnimation(getRotateAnimation());
btnRefreshAll.invalidate();
btnRefreshAll.getAnimation().startNow();
}
protected void onPostExecute(Void hello) {
btnRefreshAll.setAnimation(null);
refreshList();
}
}
Thanks for your answers guys. userSeven7s' reply also makes a lot of sense so I may use that as a backup should I run into any problems with AsyncTask.
Related
I have a problem with showing progress on ProgressBar.
I have function that calls my server with Volley, when return result I have implemented a Callback that return data to my activity.
Something like:
public static void saveData(Data data, final VolleyCallbackJsonObject callback){
...
callback.onSuccessResponse(new JSONObject());
...
}
Supposing that the function is called on cycle like:
for(int i = 1; i<=5; i++){
final int copyIndex = i;
MyClass.saveData(new Data(i), new VolleyCallbackJsonObject() {
#Override
public void onSuccessResponse(JSONObject result) {
callFunctionForShowAndUpdateProgress(copyIndex);
}
})
}
}
I would like show a ProgressBar that show a bar with a progress from 1/5 to 5/5
so I have my Function in my activityClass:
public Class Test extends Activity{
private ProgressBar pb;
public void callFunctionForShowAndUpdateProgress(int index){
if(index == 1){
pb = (ProgressBar) findViewById(R.id.pbLoading);
pb.setMax(5);
pb.setIndeterminate(false);
pb.setVisibility(View.VISIBLE);
}
if(pb != null){
pb.setProgress(index);
}
if(index == 5){
pb.postDelayed(new Runnable() {
#Override
public void run() {
pb.setVisibility(View.GONE);
}
}, 3000);
}
}
}
My problem is that progress bar not set progress, but it show only at the end so with 5/5 and not show the progress step (1/5, 2/5, 3/5, 4/5) and then it will hide after 3 seconds how specified on postDelayed.
So How can I show all the step? what is wrong?
Try to postDelay the method 'callFunctionForShowAndUpdateProgress' in the method 'onSuccessResponse' instead the postDelay you are already making because the delay you are making happens only when i = 5, so technically the progress goes through 2, 3, and 4 but you don't see it.
try this inside 'onSuccessResponse':
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
callFunctionForShowAndUpdateProgress(copyIndex);
}
}, 3000);
By the way, When the method 'callFunctionForShowAndUpdateProgress' is called in the
'onSuccessResponse' the index initially will be 1 and that is fine, the compiler then will go to second if statement
if(pb != null){
pb.setProgress(index);
}
and will execute it because the condition is correct there, it will not cause a problem but leaving it like this is not a good practice, to solve this prevent the compiler from getting into this if statement by adding else.
I hope that helps
Change your cycle calling function as like below and then check,
int copyIndex = 1;
callFunctionForShowAndUpdateProgress(copyIndex);
for(int i = 1; i<=5; i++)
{
MyClass.saveData(new Data(i), new VolleyCallbackJsonObject()
{
#Override
public void onSuccessResponse(JSONObject result)
{
copyIndex++;
callFunctionForShowAndUpdateProgress(copyIndex);
}
})
}
I have an asynctask and inside doInBackground, I don't have a for/while loop. Instead, I have a different class for it and that class generates a list using a for loop. So how can I update UI with onProgressUpdate?
Here is:
#Override
protected List<MyObject> doInBackground(Void... voids) {
IAppLogic appLogic = new AppLogic(mContext);
List<MyObject> list = appLogic.getApps(ALL_APPS);
return list;
}
MyObject is a custom object and IAppLogic is the interface of the class that gets installed applications.
You can implement this by giving the getApps()-method a callback-parameter. Pseudo code:
interface AppFoundCallback {
public onAppFound(App app, int progress);
}
// in AppLogic.getApps:
public getApps(String filter, AppFoundCallback callback)
for (int i = 0; i < listOfApps.length; i++) {
// Do the work you need to do here.
int progress = (i / listOfApps.length) * 100;
callback.onAppFound(app, progress);
}
}
// in your Task:
class Task extends AsyncTask implements AppFoundCallback {
// Implement the callback
#Override
onAppFound(App app, int progress) {
this.publishProgress(progress);
}
// Setup and register the listener
#Override
protected void doInBackground() {
// Ohter stuff
List<MyObject> list = appLogic.getApps(ALL_APPS, this);
}
// Render progress updates on the UI
#Override
protected void onProgressUpdate(Integer... progress) {
progressView.setProgress(progress[0]);
}
}
Simply put: your code notifies the caller of the getApps()-method every time you find something. This will then be published as a progress-update. The publishProgress()-method is part of AsyncTask and will take care of calling onProgressUpdate() on the UI-Thread, so that you can simply update your views.
Hi i have a function to get users from website database
my function
private void get_users() {
try {
url = "my address";
dbGetData3 = new DbGetData();
new Thread(new Runnable() {
public void run() {
data = dbGetData3.getDataFromDB(url);
runOnUiThread(new Runnable() {
#Override
public void run() {
userha = parseJSON3(data);
}
});
}
}).start();
Toast.makeText(context, "please wait ", Toast.LENGTH_LONG)
.show();
} catch (Exception e) {
toast(9);
}
Now i want add a loading progress bar while fetch data finished.
I use AsyncTask like this:
private class LongOperation extends AsyncTask<String, Void, String> {
protected void onPreExecute() {
progressDialog = new ProgressDialog(Login.this);
progressDialog.setTitle("Processing...");
progressDialog.setMessage("Please wait...");
progressDialog.setCancelable(true);
progressDialog.show();
}
protected String doInBackground(String... params) {
try {
get_users();
} catch (Exception e) {
}
return null;
}
protected void onPostExecute(String result) {
progressDialog.dismiss();
}
}
and i use this code for excute
mytask = new LongOperation();
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB)
mytask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
mytask.execute();
imageView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
onCreate(savedInstanceState);
}
});
but progress dialog dose not show for me (get user worked)
i change my code like this:
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB){
mytask.onPreExecute();
mytask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
else
{
mytask.onPreExecute();
mytask.execute();
}
then my progress dialog allways show
i test other code in stackoverflow like
AsyncTask doInBackground does not run
AsyncTask called from Handler will not execute doInBackground
Android SDK AsyncTask doInBackground not running (subclass)
but that not work for me
please help me tankyou
Consdier using a LoaderManager and an AsyncTaskLoader for this sort of stuff.
AsyncTasks are a pain in the ass as because you have to manage their lifecycle with screen-rotations etc. With a LoaderManager all of that is in the past.
Below is an example of a loader which loads a list of "items".
public class ItemsLoader extends AsyncTaskLoader<List<Item>> {
private static final String TAG = "ItemsLoader";
private List<Item> mItems;
private ItemUpdatedReceiver mObserver;
private int mSomeParam;
public static class ItemUpdatedReceiver extends BroadcastReceiver {
private static final String TAG = "ItemLoader";
final ItemsLoader mLoader;
public ItemUpdatedReceiver(ItemsLoader mLoader) {
this.mLoader = mLoader;
// listen for changes to the account we're using
IntentFilter filter = new IntentFilter(GlobalConstants.ACTION_ITEMS_UPDATED);
LocalBroadcastManager.getInstance(mLoader.getContext()).registerReceiver(this, filter);
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (GlobalConstants.ACTION_ITEMS_UPDATED.equals(action)) {
mLoader.onContentChanged();
}
}
}
public void setSomeParam(int someParam){
mSomeParam = someParam;
onContentChanged();
}
public ItemsLoader(Context context, int someParam) {
super(context);
mSomeParam = someParam;
onContentChanged();
}
#Override
public List<Item> loadInBackground() {
// do whatever you need to do here
ArrayList<Item> Items = new ArrayList<>();
return Items;
}
/**
* Called when there is new data to deliever to the client.
*
* #param data
*/
#Override
public void deliverResult(List<Item> data) {
if (isReset()) {
// an async query came in while the loader is stopped, we don't need the result
//release resources if needed
onReleaseResources(data);
}
List<Item> oldItems = mItems;
mItems = data;
if (isStarted()) {
// If the Loader is currently started, we can immediately
// deliver its results.
super.deliverResult(mItems);
}
// At this point we can release the resources associated with
// 'oldApps' if needed; now that the new result is delivered we
// know that it is no longer in use.
if (oldItems != null) {
onReleaseResources(oldItems);
}
}
#Override
protected void onStartLoading() {
super.onStartLoading();
if (mItems != null) {
// If we currently have a result available, deliver it
// immediately.
deliverResult(mItems);
}
// start listening for changes
if (mObserver == null) {
mObserver = new ItemUpdatedReceiver(this);
}
if (takeContentChanged() || mItems == null) {
// If the data has changed since the last time it was loaded
// or is not currently available, start a load.
forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
#Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to cancel a load.
*/
#Override
public void onCanceled(List<Item> items) {
super.onCanceled(items);
// At this point we can release the resources associated with 'profile'
// if needed.
onReleaseResources(items);
}
#Override
protected void onReset() {
super.onReset();
// Ensure the laoder is stopped
onStopLoading();
// At this point we can release the resources if needed.
if (mItems != null) {
onReleaseResources(mItems);
mItems = null;
}
// Stop monitoring for changes.
if (mObserver != null) {
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mObserver);
mObserver = null;
}
}
/**
* Helper function to take care of releasing resources associated
* with an actively loaded data set.
*/
private void onReleaseResources(List<Item> data) {
// For a simple List<> there is nothing to do. For something
// like a Cursor, we would close it here.
}
}
To use this class, in your activity you must extend LoaderManager.LoaderCallbacks> and override the methods:
public Loader<List<Item>> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// start the loading dialog here
return new ItemsLoader(context);
}
public void onLoadFinished(Loader<List<Item>> loader, List<Item>data) {
// do something with your data, hide the progress dialog
}
public void onLoaderReset(Loader<Cursor> loader) {
// set the old data to null
}
To actually start loading:
getLoaderManager().initLoader(LOADER_ID, null, this);
I have one big problem that is bugging me for a couple of days right now. In the application I am working on, I have couple of activities, and one of them is central (BaseActivity) from which the app goes to other Activities and in each of them it works with Volley Library to fetch data from the API.
So for instance, if I go from BaseActivity to SelectionActivity, in SelectionActivity I receive my recycle view with all the necessary data. That is quite alright, that's what I need.
However, when I finish the activity either by clicking back button or home button on the toolbar, and then want to return back to the SelectionActivity again, the data is not being loaded again. It doesn't state any exception, just doesn't load anything.
I have been searching all over the Internet but I can't seem to find the solution.
EDIT: OK further when I investigated with the debugger. It seems like everything should work because it goes through the response, and through everything. However, instead of RecyclerView filled with data, I don't see anything.
SelectionController Method:
public void getAllJobs()
{
queue = VolleyRequestHandler.getInstance(false).getQueue();
JsonArrayRequest jobsRequest = new JsonArrayRequest(Request.Method.GET,makeAllJobsRequest(),
new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
List<Job> jobs = new ArrayList<>();
Job job;
try
{
for (int i=0; i<response.length(); i++)
{
JSONObject dbJob = response.getJSONObject(i);
job = new Job();
job.setId(dbJob.getLong("JobId"));
job.setName(dbJob.getString("JobText"));
job.setCustName(dbJob.getString("CustomerName"));
jobs.add(job);
}
// Entries are being sorted!
Collections.sort(jobs, new CustomerComparator());
injection.onGettingAllJobs(jobs);
}
catch (JSONException e)
{
e.printStackTrace();
injection.onErrorSelection(e.getMessage());
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
injection.onErrorSelection(error.getMessage());
}
});
jobsRequest.setTag(injection.GET_JOBS_TAG);
jobsRequest.setRetryPolicy(new DefaultRetryPolicy(
(int) TimeUnit.SECONDS.toMillis(10),//time out in 10second
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,//DEFAULT_MAX_RETRIES = 1;
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
queue.add(jobsRequest);
}
SelectionInjection interface:
public interface SelectionInjection
{
public static final String GET_JOBS_TAG = "SELECTION_GET_JOBS_TAG";
public static final String GET_TASKS_TAG = "SELECTION_GET_TASKS_TAG";
public static final String SAVE_ENTRY_TAG ="SELECTION_SAVE_ENTRY_TAG";
public void onGettingAllJobs(List<Job> jobs);
public void onGettingTasksForJob(List<Task> tasks);
public void onSavedEntry(TimeEntry savedEntry);
public void onErrorSelection(String message);
}
SelectionActivity:
public class SelectionActivity extends ActionBarActivity implements SelectionInjection {
private static final String TAG = SelectionActivity.class.getName();
/*
* Init variables for Recycler Views
* */
private LinearLayoutManager mLayoutManager;
private SelectJobAdapter mJobsAdapter;
private StickyHeadersItemDecoration headers;
private SelectTaskAdapter mSelectTaskAdapter;
/*
* Lists used for adapter and for storing information from server
* */
private List<Job> mJobs;
private List<Task> mTasks;
private SelectionController controller;
private Job selectedJob;
// Inject Views with ButterKnife
#InjectView(R.id.select_job_recyclerview) SuperRecyclerView mJobSuperRecyclerView;
#InjectView(R.id.select_task_recyclerview) SuperRecyclerView mTaskSuperRecyclerView;
#InjectView(R.id.job_view)FrameLayout mJobView;
#InjectView(R.id.task_view) FrameLayout mTaskView;
#InjectView(R.id.toolbar_actionbar) Toolbar mToolbarAction;
private int mAnimDuration;
// private SelectionTask mFetchingTask; // Is it a good idea to Init it here? -> Nej
// private SaveSelectionTask mSavingTask;
// TODO: Figure out why after coming back again to this activity, there is only a loading wheel and nothing shows up
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_selection);
ButterKnife.inject(this);
/*
* Setting up stuff for ViewSwitching
* */
mTaskView.setVisibility(View.GONE); // Also set in XMl but just in case :D
mAnimDuration = getResources()
.getInteger(android.R.integer.config_mediumAnimTime);
/*
* Setting Up Action Bar
* */
mToolbarAction.setTitle(getString(R.string.select_job_title));
setSupportActionBar(mToolbarAction);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true); // Hierarchical activity
if (mJobs == null) {
// provifing dummy test data
Log.v(TAG, "mJobs are are null");
// mJobs = new ArrayList<Job>();
// List<Task> taskList = new ArrayList<Task>();
// taskList.add(
// new Task(88,"Task Name Doh")
// );
// taskList.add(
// new Task(99,"Another Task Name Doh")
// );
// mJobs.add(
// new Job(
// 10,
// "Test Job",
// 1337,
// "Fake Customer",
// taskList
// ));
fetchJobs();
}
else
{
setUpJobRecyclerView();
}
}
private void fetchJobs() {
Log.v(TAG,"fetchJobs();");
// mFetchingTask = new SelectionTask();
// mFetchingTask.execute(); // No PAram, get all the jobs
controller = SelectionController.getInstance(BaseActivity.currentUser,this);
controller.getAllJobs();
}
private void fetchTasks(Job job){
Log.v(TAG,"fetchTAsks()");
try{
// mFetchingTask = new SelectionTask();
// mFetchingTask.execute(job);
controller = SelectionController.getInstance(BaseActivity.currentUser,this);
controller.getTasksForJob(job);
}catch (Exception e){
Log.v(TAG,"There was an error fetching tasks");
e.printStackTrace();
Toast.makeText(this,"There was error fetching tasks",Toast.LENGTH_LONG).show();
}
}
/**
* Method for setting up Job Recycler View
*/
private void setUpJobRecyclerView(){
/*
* Setting up Jobs Recycler View
* */
mJobsAdapter = new SelectJobAdapter(mJobs);
mJobsAdapter.setHasStableIds(true);
headers = new StickyHeadersBuilder()
.setAdapter(mJobsAdapter)
.setRecyclerView(mJobSuperRecyclerView.getRecyclerView())
.setStickyHeadersAdapter(new SelectJobHeaderAdapter(mJobs))
.build();
mJobSuperRecyclerView.setAdapter(mJobsAdapter);
mJobSuperRecyclerView.addItemDecoration(headers);
mLayoutManager = new LinearLayoutManager(this);
mJobSuperRecyclerView.setLayoutManager(mLayoutManager);
// Setting up onClickListener
mJobSuperRecyclerView.
addOnItemTouchListener(
new RecyclerUtils.RecyclerItemClickListener(
this,
new RecyclerUtils.RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
selectedJob = mJobs.get(position);
showMaterialDialog(position);
}
}));
}
/**
* Method used for settigng up and initalising all the adapters
* for TaskRecyclerView
* */
private void setUpTaskRecyclerView() {
mLayoutManager = new LinearLayoutManager(this);
mTaskSuperRecyclerView.setLayoutManager(mLayoutManager);
mSelectTaskAdapter = new SelectTaskAdapter(mTasks);
mSelectTaskAdapter.setHasStableIds(true);
headers = new StickyHeadersBuilder()
.setAdapter(mSelectTaskAdapter)
.setRecyclerView(mTaskSuperRecyclerView.getRecyclerView())
.setStickyHeadersAdapter(new SelectTaskHeaderAdapter(mTasks))
.build();
mTaskSuperRecyclerView.setAdapter(mSelectTaskAdapter);
mTaskSuperRecyclerView.addItemDecoration(headers);
mTaskSuperRecyclerView.
addOnItemTouchListener(
new RecyclerUtils.RecyclerItemClickListener(
this,
new RecyclerUtils.RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
Log.v(TAG,"TaskRecyclerView onItemClick");
Toast.makeText(getApplicationContext(),"The Task has been added",Toast.LENGTH_LONG).show();
// mSavingTask = new SaveSelectionTask();
// mSavingTask.execute(mTasks.get(position));
}
}));
}
/**
* A method that starts a corssfade Animation between JobView and TaskView
*
*/
private void crossfadeViews(final View fromView, View toView){
// Set the Task view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
toView.setAlpha(0f);
toView.setVisibility(View.VISIBLE);
// Animate the Task view to 100% opacity, and clear any animation
// listener set on the view.
toView.animate()
.alpha(1f)
.setDuration(mAnimDuration)
.setListener(null);
// Animate the Job view to 0% opacity. After the animation ends,
// set its visibility to GONE as an optimization step (it won't
// participate in layout passes, etc.)
fromView.animate()
.alpha(0f)
.setDuration(mAnimDuration)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
fromView.setVisibility(View.GONE);
}
});
}
/**
* Method that creates and shows a Dialog
* and executes fetchTasks() if given option is picked.
*/
private void showMaterialDialog(final int position){
// TODO: Consider starting running he Async Task straight away as
// it might be likely that the user will pick "Pcik A Task" option
// and this might speed up the process
Log.v(TAG,"showMAterialDialog");
new MaterialDialog.Builder(this)
.title("Choose Action")
.positiveText("Add Job")
.negativeText("Pick A Task")
.neutralText("CANCEL")
.callback(new MaterialDialog.ButtonCallback(){
#Override
public void onPositive(MaterialDialog dialog) {
// Add Job
Log.v(TAG,"Adding the whole Job");
// mSavingTask = new SaveSelectionTask();
// mSavingTask.execute();
controller = SelectionController.getInstance(BaseActivity.currentUser,SelectionActivity.this);
controller.saveNewTimeEntry(BaseActivity.selectedDate,selectedJob,null);
}
#Override
public void onNegative(MaterialDialog dialog) {
/**
*Pick a Task
*/
fetchTasks(mJobs.get(position));
Log.v(TAG, "Switching Views");
crossfadeViews(mJobView, mTaskView);
}
#Override
public void onNeutral(MaterialDialog dialog) {
Log.v(TAG,"Cancelling the Dialog Choice");
}
}).show();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.global, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
try
{
switch(id)
{
case R.id.action_settings:
return true;
case android.R.id.home:
if (mTaskView.getVisibility() == View.VISIBLE)
{
crossfadeViews(mTaskView,mJobView);
}
else
{
finish();
}
return true;
default:
throw new Exception();
}
}
catch (Exception e)
{
e.printStackTrace();
return super.onOptionsItemSelected(item);
}
}
#Override
public void onSavedEntry(TimeEntry savedEntry)
{
Log.v("SAVED ENTRY", "TRUE");
System.out.println(savedEntry.toString());
controller.closeQueue(SAVE_ENTRY_TAG);
}
#Override
public void onGettingAllJobs(List<Job> jobs) {
mJobs = jobs;
setUpJobRecyclerView();
controller.closeQueue(GET_JOBS_TAG);
}
#Override
public void onGettingTasksForJob(List<Task> tasks) {
mTasks = tasks;
setUpTaskRecyclerView();
controller.closeQueue(GET_TASKS_TAG);
}
#Override
public void onErrorSelection(String message) {
Log.v(TAG,"onErrorJobTask");
}
}
So after a lot of debugging, I have come across to the solution:
It seemed like the way I was making Controllers and Injections didn't really fit into the Android development so I have restructured the code back into the activities and fragments and all works now.
EDIT:
Finally I have figured out what the ACTUAL problem is. When I was passing the Context to the Controller.. I only did so when I actually instantiated them. And because Controllers are singletons in my case, I have been using the old context when reentering Activity.
I can't believe how I did not get that. :)
I want to do an animation with several image-files, and for this the AnimationDrawable works very well. However, I need to know when the animation starts and when it ends (i.e add a listener like the Animation.AnimationListener). After having searched for answers, I'm having a bad feeling the AnimationDrawable does not support listeners..
Does anyone know how to create a frame-by-frame image animation with a listener on Android?
After doing some reading, I came up with this solution. I'm still surprised there isn't a listener as part of the AnimationDrawable object, but I didn't want to pass callbacks back and forward so instead I created an abstract class which raises an onAnimationFinish() method. I hope this helps someone.
The custom animation drawable class:
public abstract class CustomAnimationDrawableNew extends AnimationDrawable {
/** Handles the animation callback. */
Handler mAnimationHandler;
public CustomAnimationDrawableNew(AnimationDrawable aniDrawable) {
/* Add each frame to our animation drawable */
for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) {
this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i));
}
}
#Override
public void start() {
super.start();
/*
* Call super.start() to call the base class start animation method.
* Then add a handler to call onAnimationFinish() when the total
* duration for the animation has passed
*/
mAnimationHandler = new Handler();
mAnimationHandler.post(new Runnable() {
#Override
public void run() {
onAnimationStart();
}
};
mAnimationHandler.postDelayed(new Runnable() {
#Override
public void run() {
onAnimationFinish();
}
}, getTotalDuration());
}
/**
* Gets the total duration of all frames.
*
* #return The total duration.
*/
public int getTotalDuration() {
int iDuration = 0;
for (int i = 0; i < this.getNumberOfFrames(); i++) {
iDuration += this.getDuration(i);
}
return iDuration;
}
/**
* Called when the animation finishes.
*/
public abstract void onAnimationFinish();
/**
* Called when the animation starts.
*/
public abstract void onAnimationStart();
}
To use this class:
ImageView iv = (ImageView) findViewById(R.id.iv_testing_testani);
iv.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
// Pass our animation drawable to our custom drawable class
CustomAnimationDrawableNew cad = new CustomAnimationDrawableNew(
(AnimationDrawable) getResources().getDrawable(
R.drawable.anim_test)) {
#Override
void onAnimationStart() {
// Animation has started...
}
#Override
void onAnimationFinish() {
// Animation has finished...
}
};
// Set the views drawable to our custom drawable
v.setBackgroundDrawable(cad);
// Start the animation
cad.start();
}
});
I needed to know when my one-shot AnimationDrawable completes, without having to subclass AnimationDrawable since I must set the animation-list in XML. I wrote this class and tested it on Gingerbread and ICS. It can easily be extended to give a callback on each frame.
/**
* Provides a callback when a non-looping {#link AnimationDrawable} completes its animation sequence. More precisely,
* {#link #onAnimationComplete()} is triggered when {#link View#invalidateDrawable(Drawable)} has been called on the
* last frame.
*
* #author Benedict Lau
*/
public abstract class AnimationDrawableCallback implements Callback {
/**
* The last frame of {#link Drawable} in the {#link AnimationDrawable}.
*/
private Drawable mLastFrame;
/**
* The client's {#link Callback} implementation. All calls are proxied to this wrapped {#link Callback}
* implementation after intercepting the events we need.
*/
private Callback mWrappedCallback;
/**
* Flag to ensure that {#link #onAnimationComplete()} is called only once, since
* {#link #invalidateDrawable(Drawable)} may be called multiple times.
*/
private boolean mIsCallbackTriggered = false;
/**
*
* #param animationDrawable
* the {#link AnimationDrawable}.
* #param callback
* the client's {#link Callback} implementation. This is usually the {#link View} the has the
* {#link AnimationDrawable} as background.
*/
public AnimationDrawableCallback(AnimationDrawable animationDrawable, Callback callback) {
mLastFrame = animationDrawable.getFrame(animationDrawable.getNumberOfFrames() - 1);
mWrappedCallback = callback;
}
#Override
public void invalidateDrawable(Drawable who) {
if (mWrappedCallback != null) {
mWrappedCallback.invalidateDrawable(who);
}
if (!mIsCallbackTriggered && mLastFrame != null && mLastFrame.equals(who.getCurrent())) {
mIsCallbackTriggered = true;
onAnimationComplete();
}
}
#Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (mWrappedCallback != null) {
mWrappedCallback.scheduleDrawable(who, what, when);
}
}
#Override
public void unscheduleDrawable(Drawable who, Runnable what) {
if (mWrappedCallback != null) {
mWrappedCallback.unscheduleDrawable(who, what);
}
}
//
// Public methods.
//
/**
* Callback triggered when {#link View#invalidateDrawable(Drawable)} has been called on the last frame, which marks
* the end of a non-looping animation sequence.
*/
public abstract void onAnimationComplete();
}
Here is how to use it.
AnimationDrawable countdownAnimation = (AnimationDrawable) mStartButton.getBackground();
countdownAnimation.setCallback(new AnimationDrawableCallback(countdownAnimation, mStartButton) {
#Override
public void onAnimationComplete() {
// TODO Do something.
}
});
countdownAnimation.start();
Animation end can be easily tracked by overriding selectDrawable method in AnimationDrawable class. Complete code is the following:
public class AnimationDrawable2 extends AnimationDrawable
{
public interface IAnimationFinishListener
{
void onAnimationFinished();
}
private boolean finished = false;
private IAnimationFinishListener animationFinishListener;
public IAnimationFinishListener getAnimationFinishListener()
{
return animationFinishListener;
}
public void setAnimationFinishListener(IAnimationFinishListener animationFinishListener)
{
this.animationFinishListener = animationFinishListener;
}
#Override
public boolean selectDrawable(int idx)
{
boolean ret = super.selectDrawable(idx);
if ((idx != 0) && (idx == getNumberOfFrames() - 1))
{
if (!finished)
{
finished = true;
if (animationFinishListener != null) animationFinishListener.onAnimationFinished();
}
}
return ret;
}
}
I used a recursive function that checks to see if the current frame is the last frame every timeBetweenChecks milliseconds.
private void checkIfAnimationDone(AnimationDrawable anim){
final AnimationDrawable a = anim;
int timeBetweenChecks = 300;
Handler h = new Handler();
h.postDelayed(new Runnable(){
public void run(){
if (a.getCurrent() != a.getFrame(a.getNumberOfFrames() - 1)){
checkIfAnimationDone(a);
} else{
Toast.makeText(getApplicationContext(), "ANIMATION DONE!", Toast.LENGTH_SHORT).show();
}
}
}, timeBetweenChecks);
}
A timer is a bad choice for this because you will get stuck trying to execute in a non UI thread like HowsItStack said. For simple tasks you can just use a handler to call a method at a certain interval. Like this:
handler.postDelayed(runnable, duration of your animation); //Put this where you start your animation
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
handler.removeCallbacks(runnable)
DoSomethingWhenAnimationEnds();
}
};
removeCallbacks assures this only executes once.
This is so simple when it come to using Kotlin, AnimationDrawable has two functions we could use to calculate the animation duration, then we could add a runnable with delay to create an Animation listener. here is a simple Kotlin extension.
fun AnimationDrawable.onAnimationFinished(block: () -> Unit) {
var duration: Long = 0
for (i in 0..numberOfFrames) {
duration += getDuration(i)
}
Handler().postDelayed({
block()
}, duration + 200)
}
if you want to impliment your animation in adapter - should use next
public class CustomAnimationDrawable extends AnimationDrawable {
/**
* Handles the animation callback.
*/
Handler mAnimationHandler;
private OnAnimationFinish onAnimationFinish;
public void setAnimationDrawable(AnimationDrawable aniDrawable) {
for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) {
this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i));
}
}
public void setOnFinishListener(OnAnimationFinish onAnimationFinishListener) {
onAnimationFinish = onAnimationFinishListener;
}
#Override
public void stop() {
super.stop();
}
#Override
public void start() {
super.start();
mAnimationHandler = new Handler();
mAnimationHandler.postDelayed(new Runnable() {
public void run() {
if (onAnimationFinish != null)
onAnimationFinish.onFinish();
}
}, getTotalDuration());
}
/**
* Gets the total duration of all frames.
*
* #return The total duration.
*/
public int getTotalDuration() {
int iDuration = 0;
for (int i = 0; i < this.getNumberOfFrames(); i++) {
iDuration += this.getDuration(i);
}
return iDuration;
}
/**
* Called when the animation finishes.
*/
public interface OnAnimationFinish {
void onFinish();
}
}
and implementation in RecycleView Adapter
#Override
public void onBindViewHolder(PlayGridAdapter.ViewHolder holder, int position) {
final Button mButton = holder.button;
mButton.setBackgroundResource(R.drawable.animation_overturn);
final CustomAnimationDrawable mOverturnAnimation = new CustomAnimationDrawable();
mOverturnAnimation.setAnimationDrawable((AnimationDrawable) mContext.getResources().getDrawable(R.drawable.animation_overturn));
mOverturnAnimation.setOnFinishListener(new CustomAnimationDrawable.OnAnimationFinish() {
#Override
public void onFinish() {
// your perform
}
});
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(final View v) {
mOverturnAnimation.start();
}
});
}
I also like Ruslan's answer, but I had to make a couple of changes in order to get it to do what I required.
In my code, I have got rid of Ruslan's finished flag, and I have also utilised the boolean returned by super.selectDrawable().
Here's my code:
class AnimationDrawableWithCallback extends AnimationDrawable {
interface IAnimationFinishListener {
void onAnimationChanged(int index, boolean finished);
}
private IAnimationFinishListener animationFinishListener;
public IAnimationFinishListener getAnimationFinishListener() {
return animationFinishListener;
}
void setAnimationFinishListener(IAnimationFinishListener animationFinishListener) {
this.animationFinishListener = animationFinishListener;
}
#Override
public boolean selectDrawable(int index) {
boolean drawableChanged = super.selectDrawable(index);
if (drawableChanged && animationFinishListener != null) {
boolean animationFinished = (index == getNumberOfFrames() - 1);
animationFinishListener.onAnimationChanged(index, animationFinished);
}
return drawableChanged;
}
}
And here is an example of how to implement it...
public class MyFragment extends Fragment implements AnimationDrawableWithCallback.IAnimationFinishListener {
#Override
public void onAnimationChanged(int index, boolean finished) {
// Do whatever you need here
}
}
If you only want to know when the first cycle of animation has completed, then you can set a boolean flag in your fragment/activity.
I guess your Code does not work, because you try to modify a View from a non-UI-Thread. Try to call runOnUiThread(Runnable) from your Activity. I used it to fade out a menu after an animation for this menu finishes. This code works for me:
Animation ani = AnimationUtils.loadAnimation(YourActivityNameHere.this, R.anim.fadeout_animation);
menuView.startAnimation(ani);
// Use Timer to set visibility to GONE after the animation finishes.
TimerTask timerTask = new TimerTask(){
#Override
public void run() {
YourActivityNameHere.this.runOnUiThread(new Runnable(){
#Override
public void run() {
menuView.setVisibility(View.GONE);
}
});}};
timer.schedule(timerTask, ani.getDuration());
You can use registerAnimationCallback to check your animation start and end.
Here's the snippet code:
// ImageExt.kt
fun ImageView.startAnim(block: () -> Unit) {
(drawable as Animatable).apply {
registerAnimationCallback(
drawable,
object : Animatable2Compat.AnimationCallback() {
override fun onAnimationStart(drawable: Drawable?) {
block.invoke()
isClickable = false
isEnabled = false
}
override fun onAnimationEnd(drawable: Drawable?) {
isClickable = true
isEnabled = true
}
})
}.run { start() }
}
// Fragment.kt
imageView.startAnim {
// do something after the animation ends here
}
The purpose of the ImageExt was to disable after animation start (on progress) to prevent user spamming the animation and resulting in the broken / wrong vector shown.
With frame-by-frame, you might want to trigger another ImageView like this.
// Animation.kt
iv1.startAnim {
iv2.startAnim {
// iv3, etc
}
}
But the above solutions looks ugly. If anyone has a better approach, please comment below, or edit this answer directly.
I had the same problem when I had to implement a button click after animation stopped. I checked the current frame and the lastframe of animation drawable to know when an animation is stopped. Note that it is not a listener but just a way to know it animation has stopped.
if (spinAnimation.getCurrent().equals(
spinAnimation.getFrame(spinAnimation
.getNumberOfFrames() - 1))) {
Toast.makeText(MainActivity.this, "finished",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "Not finished",
Toast.LENGTH_SHORT).show();
}
I don't know about all these other solutions, but this is the one that comes closest to simply adding a listener to the AnimationDrawable class.
class AnimationDrawableListenable extends AnimationDrawable{
static interface AnimationDrawableListener {
void selectIndex(int idx, boolean b);
}
public AnimationDrawableListener animationDrawableListener;
public boolean selectDrawable(int idx) {
boolean selectDrawable = super.selectDrawable(idx);
animationDrawableListener.selectIndex(idx,selectDrawable);
return selectDrawable;
}
public void setAnimationDrawableListener(AnimationDrawableListener animationDrawableListener) {
this.animationDrawableListener = animationDrawableListener;
}
}
I prefer not to go for timing solution, as it seems to me isn't reliable enough.
I love Ruslan Yanchyshyn's solution : https://stackoverflow.com/a/12314579/72437
However, if you notice the code carefully, we will receive animation end callback, during the animation start of last frame, not the animation end.
I propose another solution, by using a dummy drawable in animation drawable.
animation_list.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="#drawable/card_selected_material_light" android:duration="#android:integer/config_mediumAnimTime" />
<item android:drawable="#drawable/card_material_light" android:duration="#android:integer/config_mediumAnimTime" />
<item android:drawable="#drawable/dummy" android:duration="#android:integer/config_mediumAnimTime" />
</animation-list>
AnimationDrawableWithCallback.java
import android.graphics.drawable.AnimationDrawable;
/**
* Created by yccheok on 24/1/2016.
*/
public class AnimationDrawableWithCallback extends AnimationDrawable {
public AnimationDrawableWithCallback(AnimationDrawable aniDrawable) {
/* Add each frame to our animation drawable */
for (int i = 0; i < aniDrawable.getNumberOfFrames(); i++) {
this.addFrame(aniDrawable.getFrame(i), aniDrawable.getDuration(i));
}
}
public interface IAnimationFinishListener
{
void onAnimationFinished();
}
private boolean finished = false;
private IAnimationFinishListener animationFinishListener;
public void setAnimationFinishListener(IAnimationFinishListener animationFinishListener)
{
this.animationFinishListener = animationFinishListener;
}
#Override
public boolean selectDrawable(int idx)
{
if (idx >= (this.getNumberOfFrames()-1)) {
if (!finished)
{
finished = true;
if (animationFinishListener != null) animationFinishListener.onAnimationFinished();
}
return false;
}
boolean ret = super.selectDrawable(idx);
return ret;
}
}
This is how we can make use of the above class.
AnimationDrawableWithCallback animationDrawable2 = new AnimationDrawableWithCallback(rowLayoutAnimatorList);
animationDrawable2.setAnimationFinishListener(new AnimationDrawableWithCallback.IAnimationFinishListener() {
#Override
public void onAnimationFinished() {
...
}
});
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(animationDrawable2);
} else {
view.setBackgroundDrawable(animationDrawable2);
}
// https://stackoverflow.com/questions/14297003/animating-all-items-in-animation-list
animationDrawable2.setEnterFadeDuration(this.configMediumAnimTime);
animationDrawable2.setExitFadeDuration(this.configMediumAnimTime);
animationDrawable2.start();
i had used following method and it is really works.
Animation anim1 = AnimationUtils.loadAnimation( this, R.anim.hori);
Animation anim2 = AnimationUtils.loadAnimation( this, R.anim.hori2);
ImageSwitcher isw=new ImageSwitcher(this);
isw.setInAnimation(anim1);
isw.setOutAnimation(anim2);
i hope this will solve your problem.