Tuesday, December 13, 2016

Presenter callback in the middle of rotation


This is my answer to this question.

First of all I use this cool method to keep presenter alive even if activity recreated: Presenter surviving orientation changes with Loaders. It detaches and attaches activity in onStop and onStart.
Need to mention also, that your second choice with persistent fragment in widely used, e.g. by Fernando Cejas. I've learned clean architecture approach with his articlesand he uses setRetainState(true).
And still your question is driving me crazy as well. Only solution I've found so far is ugly as hell. But it should work. Idea: after work done, I check if view is attached. If so, I proceed normally. I there is no view, that we are in the middle of rotation. So I have flag, that indicate, that work is done. I turn it on. Also I cache any needed data. And wait for the next view attaching. Where I check that flag.
Here is my code snippet. I'm not proud of it thought.
class SplashPresenter extends BasePresenter<SplashView> {

    private final SplashInteractor splashInteractor;
    private boolean isSplashWorkStarted;
    private boolean isSplashWorkFinished;
    private boolean isSplashWorkError;
    private Throwable splashWorkError;

    @Inject
    SplashPresenter(SplashInteractor splashInteractor) {
        this.splashInteractor = splashInteractor;
    }

    @Override
    public void attachView(SplashView mvpView) {
        super.attachView(mvpView);
        if (isSplashWorkFinished) {
            getMvpView().showApplicationUi();
        } else if (isSplashWorkError) {
            getMvpView().showError(splashWorkError.getMessage());
        }
    }

    void executeSplashWork() {
        if (!isSplashWorkStarted) {
            splashInteractor.execute(new SplashInteractorSubscriber());
            isSplashWorkStarted = true;
        }
    }

    @Override
    public void onDestroyed() {
        splashInteractor.unsubscribe();
    }

    private final class SplashInteractorSubscriber extends Subscriber<Void> {
        @Override
        public void onCompleted() {
            if (isViewAttached()) {
                getMvpView().showApplicationUi();
            } else {
                isSplashWorkFinished = true;
            }
        }

        @Override
        public void onError(Throwable e) {
            if (isViewAttached()) {
                getMvpView().showError(e.getMessage());
            } else {
                isSplashWorkError = true;
                splashWorkError = e;
            }
        }

        @Override
        public void onNext(Void v) {
        }
    }
}

Wednesday, May 25, 2016

Android Memo 4. Notify your RecyclerView

You should notify your RecyclerView about ANY change of data. E.g. if you clear adapter and then add items, you should call notifyItem* TWICE.

Saturday, April 2, 2016

Android Memo 3. How to change dialog theme?


new AlertDialog.Builder(new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault_Light_Dialog));

Tuesday, March 1, 2016

Android Memo 1

If you unregister any observers or listeners in Activity.onStop(), you should register them in Activity.onResume(), not in onCreate().

Wednesday, February 3, 2016

When you realize it's time for Dagger


actionsListener = new SplashPresenter(new DummyInMemoryRepository(
new AccelerateServiceApiImpl()), this);

Thursday, January 14, 2016

Clip to Padding.

I was wondering, how to add empty space to the end of ListView for banner. And show this space only if ListView is scrolled to bottom.

It turned out easier than I thought.
<ListView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="50dp"
    android:clipToPadding="false"/>

Saturday, August 15, 2015

Custom options menu in Android application.

First of all I need to say, that I was cunning with title. This post is not about menu customization. It's about creating pseudo-menu.
So, requirement was to create screen-wide menu with icons. Also from image you can see, that menu appears below toolbar and doesn't overlay it.

Of course I googled this question. And appears, that Google want us to keep menu grey, flat and simple. Here is interesting article about this problem:
Android: How can you Implement a Custom Menu class.
I can't resist to quote it:
My question is to Google engineers.  Why?  Why is this so?  Why can’t I pass in a custom Theme to my Menus?  Why can’t I choose how to Style them?  Yes, I can understand you not wanting some devious developer taking control of a phone by disabling the actions of all the phone’s buttons but the Menu button?
My idea is pretty simple. Instead of menu I use Fragment, that I show in every activity on press on menu icon. I use Relative layout to place this MenuFragment below Toolbar. And yes, I use Toolbar from support library instead of action bar. This gives more flexibility.

But let's explore some code.

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent">  
   <include  
     android:id="@+id/app_bar"  
     layout="@layout/app_bar" />  
   <fragment xmlns:tools="http://schemas.android.com/tools"  
     android:id="@+id/fragment"  
     android:name="com.shakenbeer.customoptionsmenu.MainActivityFragment"  
     android:layout_width="match_parent"  
     android:layout_height="match_parent"  
     android:layout_below="@id/app_bar"  
     tools:layout="@layout/fragment_main" />  
   <FrameLayout  
     android:id="@+id/fragment_menu_container"  
     android:layout_width="match_parent"  
     android:layout_height="wrap_content"  
     android:layout_below="@id/app_bar"></FrameLayout>  
 </RelativeLayout>  

This is how activity could look like. I used the same layout for every activity in my project. FrameLayout is a container for menu.
Also here is BaseActivity classm that contains logic for calling menu and choosing menu item:
public class BaseActivity extends AppCompatActivity implements MenuFragment.MenuListener {
    protected Toolbar toolbar;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toolbar = (Toolbar) findViewById(R.id.app_bar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayShowTitleEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        getSupportActionBar().setElevation(0f);
    }

    @Override    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);
        return true;
    }

    @Override    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_menu: {
                triggerFragmentMenu();
                return true;
            }
        }
        return super.onOptionsItemSelected(item);
    }

    private void triggerFragmentMenu() {
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        Fragment menuFragment = fragmentManager.findFragmentById(R.id.fragment_menu_container);
        if (menuFragment == null) {
            MenuFragment fragment = MenuFragment.newInstance();
            fragmentTransaction.replace(R.id.fragment_menu_container, fragment);
        } else {
            fragmentTransaction.remove(menuFragment);
        }
        fragmentTransaction.commit();
    }

    @Override    public void onMenuItemSelected(int menuPosition) {
        removeMenuFragment();
        MenuContent.MenuItem menuItem = MenuContent.ITEMS.get(menuPosition);
        Intent intent = new Intent(this, menuItem.activityClass);
        startActivity(intent);
    }

    @Override    public void onBackPressed() {
        if (!removeMenuFragment())
            super.onBackPressed();
    }

    private boolean removeMenuFragment() {
        FragmentManager fragmentManager = getFragmentManager();
        Fragment menuFragment = fragmentManager.findFragmentById(R.id.fragment_menu_container);
        if (menuFragment != null) {
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.remove(menuFragment);
            fragmentTransaction.commit();
            return true;
        } else {
            return false;
        }
    }}

Menu contains only one item and this item is always shown as action. This simple trick removes standard overflow menu icon from toolbar.

When we click on menu icon - we add MenuFragment to activity or remove it if it's already here.

When we click on menu item - we remove MenuFragment and start corresponding activity.
Here is sources of MenuFragment:
 public class MenuFragment extends Fragment {

    public interface MenuListener {
        public void onMenuItemSelected(int menuPosition);
    }

    private MenuListener listener;

    public static MenuFragment newInstance() {
        MenuFragment fragment = new MenuFragment();
        return fragment;
    }

    public MenuFragment() {
        // Required empty public constructor    }

    @Override    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_menu, container, false);

        ListView menuListView = (ListView) view.findViewById(R.id.fragment_menu_listview);

        menuListView.setAdapter(new MenuAdapter(getActivity()));

        menuListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override 
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                listener.onMenuItemSelected(position);
            }
        });

        return view;
    }

    @Override    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            listener = (MenuListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement MenuListener");
        }
    }
} 



MenuFrament contains MenuListener, any activity should implements it and could override BaseActivity implementation do perform whatever you want. Starting new activity from menu - it's just an example.

Full sources: https://github.com/Shakenbeer/CustomOptionsMenu