com.hscardref.android.view.NodeSelectorFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.hscardref.android.view.NodeSelectorFragment.java

Source

/**
* Copyright 2013-2014 Tiancheng Hu
* 
* Licensed under the GNU Lesser General Public License, version 3.0 (LGPL-3.0, the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
*     http://opensource.org/licenses/lgpl-3.0.html
*     
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hscardref.android.view;

import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.RelativeLayout;

import com.hscardref.R;
import com.hscardref.android.adapter.NodeAdapter;
import com.hscardref.generic.common.Constant;
import com.hscardref.generic.domain.CardCompositeFilter;
import com.hscardref.generic.domain.CardFilterCollection;
import com.hscardref.generic.domain.CardNumericFilter;
import com.hscardref.generic.viewmodel.NodeSelectorViewModel;
import com.thinkalike.android.control.ComboBox;
import com.thinkalike.android.control.ComboBox.ComboBoxListener;
import com.thinkalike.generic.common.LogTag;
import com.thinkalike.generic.common.Util;
import com.thinkalike.generic.domain.NodeType;
import com.thinkalike.generic.event.PropertyChangeEvent;
import com.thinkalike.generic.event.PropertyChangeListener;
import com.thinkalike.generic.viewmodel.control.UINode;

/**
 * A list fragment representing a list of Node. This fragment also supports
 * tablet devices by allowing node item to be drag&dropped onto a
 * {@link WorkareaFragment}.
 * <p>
 * Activities containing this fragment MUST implement the {@link FragmentCallbacks}
 * interface.
 */
public class NodeSelectorFragment extends Fragment implements OnItemClickListener {

    //-- Constants and Enums --------------------------
    private static final int[] ImageResIds = new int[] { R.drawable.btn_typea, R.drawable.btn_typeb,
            R.drawable.btn_typec, R.drawable.btn_typed, R.drawable.btn_typee, R.drawable.btn_typef,
            R.drawable.btn_typeg, R.drawable.btn_typeh, R.drawable.btn_typei, R.drawable.btn_typej,
            R.drawable.btn_typek };
    private static final NodeType[] ItemValues = new NodeType[] { NodeType.TypeA, NodeType.TypeB, NodeType.TypeC,
            NodeType.TypeD, NodeType.TypeE, NodeType.TypeF, NodeType.TypeG, NodeType.TypeH, NodeType.TypeI,
            NodeType.TypeJ, NodeType.TypeK };
    private static final NodeType DefaultValue = NodeType.TypeA;
    /**
     * The serialization (saved instance state) Bundle key representing xxx
     */
    //IMPROVE: confirm is it true that all required info is managed by ViewModel and needn't to be serialized  
    private static final String STATE_XXX = "xxx";

    //-- Inner Classes and Structures --------------------------
    //Timothy [D]: don't use such platform-dependent route for event dispatching, use EventBus instead. 
    //   /**
    //    * A callback interface that all activities containing this fragment must implement. 
    //    * This mechanism allows activities to be notified of item selections.
    //    */
    //   //IMPROVE: unify name convention, "XxxCallbacks" or "XxxListener" or something like
    //   public interface FragmentCallbacks {
    //      /**
    //       * Callback for when an item has been selected.
    //       */
    //      public void onNodeSelected(String id);
    //   }

    //-- Delegates and Events --------------------------   
    //-- Instance and Shared Fields --------------------------
    //NOTE: manage instance variables of Activity/Fragment by using onSavedInstanceState() -- Android LifeCycle Management
    //private FragmentCallbacks _listenerFromActivity = null; //relative activity listen to us
    private ComboBox<NodeType> _nodeTypeSelector;
    private NodeType _currentNodeType = DefaultValue; //managed as savedInstanceState
    private ListView _lv_nodeList;
    //MVVM
    private NodeSelectorViewModel _viewModel = null;
    private PropertyChangeListener _listenToVM = null; //listen to relative ViewModel. SHOULD be a instance variable. 
    //Registration side (ViewModel) will only keep their WeakReference.

    //IMPROVE: check necessity (for Func#3)
    private FragmentCallbacks _listenerFromActivity;
    private boolean isRefresh = true;

    //-- Properties --------------------------   
    public boolean isRefresh() {
        return isRefresh;
    }

    public void setRefresh(boolean isRefresh) {
        this.isRefresh = isRefresh;
    }

    //-- Constructors --------------------------
    /**
     * Mandatory empty constructor for the fragment manager to instantiate the
     * fragment (e.g. upon screen orientation changes).
     */
    public NodeSelectorFragment() {
    }

    //-- Destructors --------------------------   
    //-- Base Class Overrides --------------------------
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Util.trace(LogTag.LifeCycleManagement, String.format("%s: onCreate", getClass().getSimpleName()));
        super.onCreate(savedInstanceState);

        //FD: 1.ICommand: calls onNodeTypeChanged() -- ref:onViewCreated()
        //    2.IPropery: observe PropertyName.NodeList changes
        if (_viewModel == null) {
            _viewModel = NodeSelectorViewModel.getInstance();
            //IMPROVE: if it's possible that parent activity changes, then the inner NodeAdapter should be 
            //  recreated or get notified (e.g. implement a NodeSelectorVMListenerBase managing uiContext and nodeType). 
            final NodeSelectorFragment thisInstance = this;
            //listen to relative ViewModel.
            _listenToVM = new PropertyChangeListener() {
                @Override
                public void onPropertyChanged(PropertyChangeEvent event) {
                    Util.trace(LogTag.ViewModel,
                            String.format("[IProperty]::View PropertyChanged(name=%s, value=%s, listener=%s)",
                                    event.getPropertyName(), event.getNewValue(),
                                    thisInstance.getClass().getSimpleName()));
                    assert (event.getPropertyName().equals(Constant.PropertyName.NodeList));
                    @SuppressWarnings("unchecked")
                    List<UINode> nodeList = (List<UINode>) event.getNewValue();
                    thisInstance.updateNodeList(nodeList);
                }
            };
            _viewModel.addPropertyChangeListener(Constant.PropertyName.NodeList, _listenToVM);
        }

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Util.trace(LogTag.LifeCycleManagement, String.format("%s: onCreateView", getClass().getSimpleName()));
        View rootView = inflater.inflate(R.layout.nodeselector, container, false); //kw: 3rd param must be false -- Android SDK

        RelativeLayout rl_nodeselector = (RelativeLayout) rootView.findViewById(R.id.rl_nodeselector);
        if (savedInstanceState != null) {
            _currentNodeType = (NodeType) savedInstanceState.getSerializable("currentNodeType"); //enum is serializable
        }
        _nodeTypeSelector = new ComboBox<NodeType>(this.getActivity(), ImageResIds, ItemValues, _currentNodeType);
        _nodeTypeSelector.registerListener(new ComboBoxListener<NodeType>() {
            @Override
            public void onSelectedItemChanged(NodeType nodeType) {
                Util.trace(LogTag.ViewModel, String.format("[ICommand]::View NodeType changed(=%s)", nodeType));
                if (_viewModel != null) {
                    _viewModel.onNodeTypeChanged(nodeType);
                    _currentNodeType = nodeType;
                }
            }
        });
        rl_nodeselector.addView(_nodeTypeSelector);
        _lv_nodeList = (ListView) rootView.findViewById(R.id.lv_nodelist);
        _lv_nodeList.setOnItemClickListener(this);

        return rootView;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        Util.trace(LogTag.LifeCycleManagement, String.format("%s: onViewCreated", getClass().getSimpleName()));
        super.onViewCreated(view, savedInstanceState);

        // Restore the previously serialized ... info.
        if (savedInstanceState != null && savedInstanceState.containsKey(STATE_XXX)) {
            //...
        }

        //_viewModel.onRefreshNodeList();
    }

    @Override
    public void onResume() {
        if (isRefresh) {
            _viewModel.onNodeTypeChanged(_currentNodeType);
            _viewModel.onRefreshNodeList();
        }
        isRefresh = true; //TODO: confirm "=true"? rename the variable to make the meaning clear.

        super.onResume();
    }

    @Override
    public void onAttach(Activity activity) {
        Util.trace(LogTag.LifeCycleManagement, String.format("%s: onAttach", getClass().getSimpleName()));
        super.onAttach(activity);

        //ViewModel may check type of parent Activity here. 
        if (!(activity instanceof FragmentCallbacks)) {
            throw new IllegalStateException("Activity must implement fragment's callbacks.");
        }

        _listenerFromActivity = (FragmentCallbacks) activity;
    }

    @Override
    public void onDetach() {
        Util.trace(LogTag.LifeCycleManagement, String.format("%s: onDetach", getClass().getSimpleName()));
        super.onDetach();

        // Reset the active callbacks interface to null.
        _listenerFromActivity = null;
    }

    @Override
    public void onDestroy() {
        Util.trace(LogTag.LifeCycleManagement, String.format("%s: onDestroy", getClass().getSimpleName()));
        super.onDestroy();
        _viewModel.removePropertyChangeListener(Constant.PropertyName.NodeList, _listenToVM);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        //NOTE: onSaveInstanceState() is not always called (such as when a user navigates back from activity B to activity A). 
        //       ref:http://forums.xamarin.com/discussion/6103/onsave-restoreinstancestate-by-back-button-pressing-not-called-but-destroyed  @TomOpgenorth
        Util.trace(LogTag.LifeCycleManagement,
                String.format("%s: onSaveInstanceState", getClass().getSimpleName()));
        outState.putSerializable("currentNodeType", _currentNodeType); //enum is serializable
        super.onSaveInstanceState(outState);
    }

    //-- Public and internal Methods --------------------------   
    //-- Private and Protected Methods --------------------------
    private void updateNodeList(List<UINode> uiNodeList) {
        UINode[] uiNodes = new UINode[0];
        //kw: ArrayList.toArray() without specifying element type will convert blank ArrayList<T> to a blank Object[], which will causes type cast exception.
        _lv_nodeList.setAdapter(new NodeAdapter(this.getActivity(), uiNodeList.toArray(uiNodes)));
        //(UINode[])(_viewModel.getUINodeList().toArray())));
    }

    //-- Event Handlers --------------------------   
    /*   kw: oops.. ListView.setOnItemSelectedListener() doesn't act as you think! use onItemClickListener instead. 
     *      ref:http://stackoverflow.com/questions/2454337/why-is-my-onitemselectedlistener-not-called-in-a-listview
       @Override
       public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
       }
       @Override
       public void onNothingSelected(AdapterView<?> parent) {
       }
    */
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        assert (parent.getAdapter() instanceof NodeAdapter);
        NodeAdapter adapter = (NodeAdapter) parent.getAdapter();
        UINode uiNode = (UINode) adapter.getItem(position);
        Util.trace(LogTag.ViewModel, String.format("[ICommand]::View Node selected(=%s)", uiNode));
        if (_viewModel != null)
            _viewModel.onNodeSelected(uiNode);
    }

    public void applyCardFilter(int id, int filterType, CardCompositeFilter cardCompositeFilter) {

        //TODO: #confirm# why not combine the compositeFilter and numericFilter within the filterCollection?
        CardFilterCollection cardFilter = _viewModel.getCardFilter();
        CardNumericFilter cardNumFilter = cardFilter.get_cardNumericFilter();

        if (id >= -1) {
            cardNumFilter.setType(filterType);

            switch (id) {
            case R.id.btn_nodefilter_all:
                cardNumFilter.setNum(-1);
                break;
            case R.id.btn_nodefilter_0:
                cardNumFilter.setNum(0);
                break;
            case R.id.btn_nodefilter_1:
                cardNumFilter.setNum(1);
                break;
            case R.id.btn_nodefilter_2:
                cardNumFilter.setNum(2);
                break;
            case R.id.btn_nodefilter_3:
                cardNumFilter.setNum(3);
                break;
            case R.id.btn_nodefilter_4:
                cardNumFilter.setNum(4);
                break;
            case R.id.btn_nodefilter_5:
                cardNumFilter.setNum(5);
                break;
            case R.id.btn_nodefilter_6:
                cardNumFilter.setNum(6);
                break;
            case R.id.btn_nodefilter_7plus:
                cardNumFilter.setNum(7);
                break;
            default:
                break;
            }
        }

        cardFilter.set_cardNumericFilter(cardNumFilter);
        if (cardCompositeFilter != null) {
            cardFilter.set_cardCompositeFilter(cardCompositeFilter);
        }
        _viewModel.setCardFilter(cardFilter);
        _viewModel.onFilterChanged();

    }

    public CardFilterCollection get_cardSearchCondition() {
        return _viewModel.getCardFilter();
    }

}