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

Wednesday, July 1, 2015

Material Design pain in arse

Google lose their own design tutorial. Material design - OK, BUT Y U NOT keep old articles, why you redirect me to new "design concept"? Where is cool tutor from this link: https://developer.android.com/design/patterns/multi-pane-layouts.html? I thought I'm going crazy, cause I could not find it after 2 days I read it. Maybe I remember something wrong, I thought. But you have this link in your own course on Udacity! And now I see this link:
http://developer.android.com/design/patterns/actionbar.html
And it works! But you can't reach this page from anywhere on developer.android.com. And it contains link to App Bar - material design analog. Why not to keep other articles in this way?
Also I remember articles with tablet design strategies - when use multiple panels and when just stretch. Maybe this was on the same page - I'm not sure.
Google, please, please, please, revert you site to previous state and keep material design as additional section. Please, please, please.

Monday, June 8, 2015

My first real-life example of Fragment's reuse.

When you start to learn Android one of the first thing, that course or book trying to put in your head - "use Fragments".
 Why? Because you can put two and more Fragments on one Activity. Ok... But I also can reuse XML code to include needed layout. This was my first thought. But I used fragments in every application. Just because I was taught.
But now I have real example of reusing Fragment for Activities, that contains exactly one Fragment.
We have applications that is simply catalog of  blogs, grouped by categories. So, on my MainActivity I have list of categories and sub-categories. And when you click on category - you get list of blogs from this category. Very simple app.



For showing these lists I use CursorLoader. So, BlogsFragment implements LoaderCallback, from onCreateLoader I return CursorLoader, that query blogs by category. This is common approach.

From screenshots you can see that there are also searching and favorites. By the way, searching implemented using FTS3, but this is theme for other post.
So, when I started to implement search, I used common android practice with searchable activity. So, I created such activity... And on search result I have to show list of blogs. Again. So I used BlogsFragment again. Only thing I have to change - it's query for cursor loader.
The same is for favorite blogs - only new query, all other code is the same.
I understand, that absolutely right approach is to use something like Strategy pattern. But for my goals I added extra flag for intent, that run activities, that use BlogsFragment. So here are some code snippets from blogs fragment:
 public class BlogsFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>  
   public static final String EXTRA_MODE = "extra_mode";  
   public static final int MODE_FROM_CATEGORY = 0;  
   public static final int MODE_FAVOURITES = 1;  
   public static final int MODE_SEARCH = 2;  
 .....  
   @Override  
   public void onActivityCreated(Bundle savedInstanceState) {  
     Intent intent = getActivity().getIntent();  
     if (intent != null) {  
       String title = intent.getStringExtra(EXTRA_CATEGORY_NAME);  
       if (title != null && !title.isEmpty())  
         getActivity().setTitle(title);  
       if (Intent.ACTION_SEARCH.equals(intent.getAction()))  
         currentMode = MODE_SEARCH;  
       else  
         currentMode = intent.getIntExtra(EXTRA_MODE, MODE_FAVOURITES);  
     }  
     initLoader();  
     super.onActivityCreated(savedInstanceState);  
   }  
 .....  
   @Override  
   public Loader<Cursor> onCreateLoader(int id, Bundle args) {  
     Intent intent = getActivity().getIntent();  
     if (intent == null)  
       return null;  
     if (currentMode == MODE_SEARCH) {  
      .....  
       return new CursorLoader(getActivity(), blogsUri, BLOGS_COLUMNS, BlogKeywordsTable.COLUMN_WORDS + " match '"  
           + query.toLowerCase(locale) + "*'", null, BlogTable.COLUMN_PRIORITY + " DESC");  
     } else if (currentMode == MODE_FROM_CATEGORY) {  
      .....   
       return new CursorLoader(getActivity(), blogsUri, BLOGS_COLUMNS, BlogTable.COLUMN_CATEGORY_1_ID + " = "  
           + categoryId, null, BlogTable.COLUMN_PRIORITY + " DESC");  
     } else /* MODE_FAVOURITES */{  
      .....   
       return new CursorLoader(getActivity(), blogsUri, BLOGS_COLUMNS, BlogTable.TABLE_NAME + "." + BlogTable._ID  
           + " in (" + favourites + ")", null, BlogTable.COLUMN_PRIORITY + " DESC");  
     }  
   }  

Thursday, August 28, 2014

Emulator online

Today I have found this awesome site: Manymo


I was trying to launch my game on emulator and failed it. So I start to search alternative. Since I need only to see app on different screens - Manymo is much better, than standard emulator. Because it is much faster - just take apk from bin and upload it. Then you have vast range of screens and apis. Choose what you need, wait 30 sec - enjoy!

Tuesday, August 26, 2014

Game Arts

Check out this arts of my new game project. Yep, I'm just coder :)