I'm working with an adapter that handles its own service call to update its items (not my adapter). This adapter defines an interface I can pass in to make callbacks to views to show/hide loading indicators. I want to show a Snacker when a call fails (example: no network connection). I've defined that method in the interface and added it to my Fragment, and I've updated the adapter to call that method when a service call fails.
#Override
public void OnLoaded(int count) {
}
#Override
public void OnStartLoading() {
showRefresh();
}
#Override
public void OnStopLoading() {
clearRefresh();
}
#Override
public void OnServiceFailure() {
showSnackBar(getString(R.string.error));
}
protected void showSnackBar(String message) {
View view = getView();
if (view != null && getUserVisibleHint()) {
snackbar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE);
snackbar.setActionTextColor(ContextCompat.getColor(getContext(), R.color.accent_dark));
snackbar.setAction(getString(R.string.retry), v -> {
refreshLayout.setRefreshing(true);
onRefresh();
});
snackbar.show();
}
}
Unfortunately, the Snackbar doesn't show.
Here's the thing: OnServiceFailure() is being called from a thread. I've tried wrapping the call to showSnackBar() in activity.runOnUiThread(), but that doesn't seem to solve the problem. I've also debugged showSnackBar() and it IS finding view and view IS the root FrameLayout.
If I call showSnackBar() anywhere else from the main thread, everything works fine.
I'm not sure where to go from here.
When using an service or thread you can only show a snackbar on the main (UI) thread. for fragments there is a special function inside the view object to post your code to the main thread.
View view = getView();
view.post()
this send your code to the main thread and shows the SnackBar.
Related
I am a newer to android, I read the blog, many people said we can not update the view in other thread, we have to update it in ui thread, what is the reason, why it reports exception when we do it? can anynoe give the reason
in the last weeks, I read the source code about how to add the view to the window.
when we call setContentView set view, it actually calls the window.setContentView and in the end, ActivityThread.handleResumeActivity will call the activity onResume method, the view shows. we look at handleResumeActivity method. it will call activity makeVisible method.
...
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
...
// the makeVieible will call wm.addView method
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
// this metod in the last called WindowGolbal.addView;then in the inner method;
// it will call ViewRootImpl.setView, in this method, it calls requestLayout
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
}
#Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
so we find here it reminds us we can not update the view. the system does that make the UI show smooth and updates view simple. if many threads can update it. it is a bad thing. because It has to handle the concurrency problem
RecyclerView calls onCreateViewHolder a bunch of times and then just keeps binding the data to these views. My view creation is slightly expensive and hence I need to defer rest of the UI tasks until my RecyclerView is done creating all the views.
I tried adding a ViewTreeObserver.OnGlobalLayoutListener but this callback gets called before even the first onCreateViewHolder() call.
Any idea how do I go about it?
After some research I've found out a solution with Handler. As you I'm looking for a beautiful code and this is a bit messy for me. But works perfectly anyway.
Handler is a class that you can use in a way to post message and/or Runnable, which will be added in a queue, then executed when that queue is finished.
My plan is, given that the adapter works on the UI, (inflate ect...) the creation and initialization (all onCreateViewHolder and onBindViewHolder) are added at a moment in the handler of the main thread.
That means that if you post a message in the main thread queue (the same obligatory used by your adapter), then the message will be executed after any previous request (after your adapted has finished to initialize everything).
Exemple :
Main activity
Initialization of the handler :
private Handler mHandler;
#Override
protected void onCreate(Bundle iSavedInstanceState) {
...
mHandler = new Handler(Looper.getMainLooper());
}
Initialization of your CustomAdapter :
private void initializeAdapter(...) {
MyCustomAdapter lMyNewAdapter = new MyCustomAdapter(...)
...
lNewAdapter.SetOnFirstViewHolderCreation(new
MyCustomAdapter.OnFirstViewHolderCreation {
#Override
public void onCreation() {
mHandler.post(new Runnable() {
#Override
public void run() {
// Finally here, the code you want to execute
// At the end of any Create and Bind VH of your
// Adapter
}
});
}
});
}
MyCustomAdapter
private boolean mIsViewHolderCreationStarted;
private OnFirstViewHolderCreation mOnFirstViewHolderCreation;
public CustomItemViewAdapter onCreateViewHolder(
#NonNull ViewGroup iViewGroup, int iI) {
...
if (!mIsViewHolderCreationStarted) {
mIsViewHolderCreationStarted = true;
if (mOnFirstViewHolderCreation != null) {
// It's at this point that we want to add a new request
// in the handler. When we're sure the request of the
// adapter has begun.
mOnFirstViewHolderCreation.onCreation();
}
}
}
public void setOnFirstViewHolderCreation(OnFirstViewHolderCreation iAction) {
mOnFirstViewHolderCreation = iAction;
}
public interface OnFirstViewHolderCreation {
void onCreation();
}
Note
Be aware that this solution will execute a code at the end of the first initialization of the enteer page that it is possible to show in a case of a RecyclerView.
A onCreateViewHolder might be called in case the screen is scrolled.
Which means that this solution does not guarantee you this handler message is executed after all possible onCreateViewHolder.
It only helps you to avoid an overload on the MainThread, during the greedy work of the adapter init.
Something else, in case you're using animations with your adapter to make it appears smoothly or something else (one of the good reasons to use this way to do), don't forget to put your RecyclerView in VISIBLE and not GONE, otherwise, the initialization of the adapter never happens.
I have a class that inherits from Application so I cannot use findViewById.
The snackbar should show, when the app is launching, so my approach was to get the root view of my launching activity and make a static method like:
public static RelativeLayout GetRootViewOfLaunchingActivity() { return root; }
Then in my class inheriting from application I did this:
public void onGetRegisteredResult(DJIError error)
{
// SDK REGISTRATION SUCCES
if(error == DJISDKError.REGISTRATION_SUCCESS)
{
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable()
{
// Show snackbar with success message
#Override
public void run()
{
Snackbar.make(LaunchingActivity.GetRootViewOfLaunchingActivity(), R.string.sdk_success, Snackbar.LENGTH_LONG).show();
}
});
Log.d("SDK REGISTRATION", "DJI SDK registered");
DJISDKManager.getInstance().startConnectionToProduct();
}
}
I mean it works just fine but I don't feel like this is the prettiest solution because I need to make the root view of my launching activity static, so my question is can anyone tell me a better way of doing this?
I am starting an activity and calling an asynctask in onCreateView() to fetch data from a webservice and populate the data in onPostExecute(). Currently the activity doesnt load until the asynctask in the fragment finishes.
How can i display the empty form immediately and update it as the task finishes? The activity just hosts fragments and the asyntasks are in the fragments, if that makes a difference.
Fragment onCreateView():
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_friends,container, false);
return v;
}
Fragment onActivityCreated():
#Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
//getFriends() calls a method that is an asyntask
peopleList = getFriends();
if (peopleList!=null){
list = (ListView)getActivity().findViewById( R.id.friendsList);
list.setAdapter(new FriendsAdapter(getActivity(), peopleList,"friends") );
}
}
This is because you are populating the data onPostExecute() which gets hooked on to the UI thread only after doInBackground() is complete. If you want to show the progress as and when things happen you need to call the publishProgress() method.
Now the publishProgress() method hooks onto the onProgressUpdate() method and this hooks onto the UI thread which updates the UI thread while doInBackground() is running. I've given a very simple example of something I did for practice sometime back - take a look at how it works. It basically keeps updating a ListActivity, one element at a time and shows this progress on screen - it does not wait until all the strings are added to display the final page:
class AddStringTask extends AsyncTask<Void, String, Void> {
#Override
protected Void doInBackground(Void... params) {
for(String item : items) {
publishProgress(item);
SystemClock.sleep(400);
}
return null;
}
#Override
protected void onProgressUpdate(String... item) {
adapter.add(items[0]);
}
#Override
protected void onPostExecute(Void unused) {
Toast.makeText(getActivity(), "Done adding string item", Toast.LENGTH_SHORT).show();
}
}
You should do as little time consuming tasks as possible in your Activity's onCreate() method. Note that your form will only been drawn after the onCreate() was 'entirely' executed.
A good idea is to start your time consuming task from a timer with a little delay (50 - 100 ms) to be sure that your onCreate() method finished executing.
Here is an example (put this instead of getFriends()):
Handler getFriendsDelayed = new Handler();
getFriendsDelayed.postDelayed(new Runnable()
{
public void run()
{
getFriends();
}
}, DELAY_IN_MILLISEC);
Hope it helps.
I have an app which request data information on internet (client-server app), but this communication is very slow, thus i have decided to create an AsyncTask to manage the delay.
inside of doInBackground i call Looper.prepare() then a my "view generator (which retrives data)".
in detail (the problem):
I have an activity that dinamically create the rows of a list view. but every time i try to inflate rows, android throws a Looper exception "Only one Looper may be created per thread"
i followed the steps:
call Looper.preapare()
use a first inflaction to create a container of my list
use a second inflaction to create a list row
I suppose I cannot inflate two times but i don't know how i can resolve that
AsyncTask
private class DrawerView extends AsyncTask<ActivityGroup, String, View>{
Exception exc=null;
#Override protected void onPreExecute() {
super.onPreExecute();
}
#Override protected View doInBackground(ActivityGroup... params) {
try {
Looper.prepare();
return processAct();
}catch (ConnectionException e) {
exc =e;
return null;
}
catch (Exception e) {
exc = e;
return null;
}
}
#Override protected void onPostExecute(View result) {
super.onPostExecute(result);
if(exc!= null){
Utils.usrMessage(getApplicationContext(), "Oh Noo!:\n"+exc.getMessage());
Utils.logErr(getApplicationContext(), exc);
finish();
}
if(result!= null){
setContentView(result);
}
}
}
processAct() is an abstract method implemented in this way
#Override protected View processAct() throws Exception {
Bundle bundle = getIntent().getExtras();
User user = (User)bundle.getSerializable("user");
Team team = Team.getTeamInformation(this,user.getTeamId());
ArrayList<Player> players =Player.getPlayerList(this,user.getTeamId());
PlayersListAdapter view = new PlayersListAdapter(this,players,team);
return view;
}
PlayerListAdapter is the class which builds/sets first view (list container)..here the first inflation
public PlayersListAdapter(Context context, ArrayList<Player> players,Team team) throws Exception{
super(context);
View view = inflate(getContext(), R.layout.team_players, this);
TextView tv_teamName = (TextView)view.findViewById(R.id.tbplrs_tmnm);
TextView tv_playersNum = (TextView)view.findViewById(R.id.tbplrs_nplrs);
tv_teamName.setText(team.getName());
String msg = players.size()+" ";
msg += (players.size()!=1)?context.getString(R.string.playerPlural):context.getString(R.string.playerSingle);
tv_playersNum.setText(msg);
ListView lView = (ListView)view.findViewById(R.id.tbplrs_plrslst);
PlayersRowListAdapter plAdapter = new PlayersRowListAdapter(context, players);
lView.setAdapter(plAdapter);
}
at last PlayerRowListAdapter which extends BaseAdapter,...here the second inflation
#Override public View getView(int position, View view, ViewGroup parent) {
if (view == null){
LayoutInflater lInflator = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = lInflator.inflate(R.layout.team_player_singlplayer,null);
}
....
....
}
N.B. if i drop the second adapter PlayerRowListAdapter...all works fine...(obviously without list)
Regards
p.s. sorry for my english
Instead of just calling Looper.prepare();, first check if Looper does not already exist for your Thread, if not, call that function. Like this:
if (Looper.myLooper()==null)
Looper.prepare();
The only reason you need to call Looper.prepare() and Looper.loop() is when you want to have a message Handler in a thread that is not the UI thread. Basically, it keeps the thread alive forever so that the Handler that was created inside the thread can still send and receive messages. The same goes for callback methods like LocationListener or something similar. You are responsible for killing the thread when it is done by calling Looper.getMyLooper().quit() inside the thread that it is in.
If you are inflating views in the UI thread, then you do not need to call Looper.prepare() or Looper.loop() as this is already done in the background. You should never inflate Views outside the UI thread.
AsyncTask already has its own Looper. If you want to update your UI from your doInBackground() method use publishProgress(..) which then invokes onProgressUpdate(..) in the main thread. In your onProgressUpdate you can inflate your Views and add them to your UI.
Edit: example code: http://developer.android.com/reference/android/os/AsyncTask.html
Here's how I worked around this.
As the Developer Reference for AsyncTask says,
doInBackground creates a new thread to manage it (this has forced me to call Looper.prepare()), while onPostExecute() uses the main thread.
So I sliced processAct() in two methods: prepareData() which retrieves data and createView() which calls adapter.
I have put the first method into doInBackground(), and the second one (createView()) I have put into onPostExecute().