Challenge 2: Solution


#1

With the help of chapter 17 and Android API Guide Dialogs, I completed Challenge 2.

Here is my solution:

SingleFragmentActivity.java
package com.bignerdranch.android.criminalintent;

import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

/**
 * Created by behin on 8/12/2017.
 */

public abstract class SingleFragmentActivity extends AppCompatActivity {

	protected abstract Fragment createFragment();

	@LayoutRes
	protected int getLayoutResId() {
		return R.layout.activity_fragment;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(getLayoutResId());

		FragmentManager fm = getSupportFragmentManager();
		Fragment fragment = fm.findFragmentById(R.id.fragment_container);

		if (fragment == null) {
			fragment = createFragment();
			fm.beginTransaction()
					.add(R.id.fragment_container, fragment)
					.commit();
		}
	}
}
CrimeFragment.java
package com.bignerdranch.android.criminalintent;


import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;

import java.util.Date;
import java.util.UUID;

import static android.widget.CompoundButton.*;

/**
 * Created by behin on 8/7/2017.
 */

public class CrimeFragment extends Fragment {
    private static final String ARG_CRIME_ID = "crime_id";
    private static final String DIALOG_DATE = "DialogDate";
    private static final int REQUEST_DATE = 0;

    private Crime mCrime;

    private EditText mTitleField;
    private Button mDateButton;
    private CheckBox mSolvedCheckBox;

    private boolean mIsLargeLayout;

    private Callbacks mCallbacks;

    /**
     * Required interface for hosting activities
     */

    public interface Callbacks {
        void onCrimeUpdated(Crime crime);
    }

    public static CrimeFragment newInstance(UUID crimeId) {
        Bundle args = new Bundle();
        args.putSerializable(ARG_CRIME_ID, crimeId);

        CrimeFragment fragment = new CrimeFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mCallbacks = (Callbacks) context;
    }

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

        mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);

        UUID crimeId = (UUID) getArguments().getSerializable(ARG_CRIME_ID);
        mCrime = CrimeLab.get(getActivity()).getCrime(crimeId);
    }

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

        mTitleField = (EditText) v.findViewById(R.id.crime_title);
        mTitleField.setText(mCrime.getTitle());
        mTitleField.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                mCrime.setTitle(charSequence.toString());
                updateCrime();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        mDateButton = (Button) v.findViewById(R.id.crime_date);
        updateDate();
        mDateButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mIsLargeLayout) {
                    FragmentManager manager = getFragmentManager();
                    DatePickerFragment dialog = DatePickerFragment.newInstance(mCrime.getDate());
                    dialog.setTargetFragment(CrimeFragment.this, REQUEST_DATE);
                    dialog.show(manager, DIALOG_DATE);
                } else {
                    Intent intent = DatePickerActivity.newIntent(getContext(), mCrime.getDate());
                    startActivityForResult(intent, REQUEST_DATE);
                }
            }
        });

        mSolvedCheckBox = (CheckBox) v.findViewById(R.id.crime_solved);
        mSolvedCheckBox.setChecked(mCrime.isSolved());
        mSolvedCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                mCrime.setSolved(b);
                updateCrime();
            }
        });

        return v;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != Activity.RESULT_OK) {
            return;
        }

        if (requestCode == REQUEST_DATE) {
            Date date = (Date) data.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
            mCrime.setDate(date);
            updateDate();
            updateCrime();
        }
    }

    private void updateCrime() {
        mCallbacks.onCrimeUpdated(mCrime);
    }

    private void updateDate() {
        mDateButton.setText(mCrime.getDate().toString());
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallbacks = null;
    }
}
CrimeListActivity.java
package com.bignerdranch.android.criminalintent;

import android.content.Intent;
import android.support.v4.app.Fragment;

/**
 * Created by behin on 8/12/2017.
 */

public class CrimeListActivity extends SingleFragmentActivity
		implements CrimeListFragment.Callbacks, CrimeFragment.Callbacks{
	@Override
	protected Fragment createFragment() {
		return new CrimeListFragment();
	}

	@Override
	protected int getLayoutResId() {
		return R.layout.activity_masterdetail;
	}

	@Override
	public void onCrimeSelected(Crime crime) {
		if (findViewById(R.id.detail_fragment_container) == null) {
			Intent intent = CrimePagerActivity.newIntent(this, crime.getId());
			startActivity(intent);
		} else {
			Fragment newDetail = CrimeFragment.newInstance(crime.getId());

			getSupportFragmentManager().beginTransaction()
					.replace(R.id.detail_fragment_container, newDetail)
					.commit();
		}
	}

	@Override
	public void onCrimeUpdated(Crime crime) {
		CrimeListFragment listFragment = (CrimeListFragment)
				getSupportFragmentManager().findFragmentById(R.id.fragment_container);
		listFragment.updateUI();
	}
}
CrimeListFragment.java
package com.bignerdranch.android.criminalintent;

import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

/**
 * Created by behin on 8/12/2017.
 */

public class CrimeListFragment extends Fragment {
	private RecyclerView mCrimeRecyclerView;
	private CrimeAdapter mAdapter;

	private Callbacks mCallbacks;

	/**
	 * Required interface for hosting activities
	 */

	public interface Callbacks {
		void onCrimeSelected(Crime crime);
	}

	@Override
	public void onAttach(Context context) {
		super.onAttach(context);
		mCallbacks = (Callbacks) context;
	}

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

		mCrimeRecyclerView = (RecyclerView) view.findViewById(R.id.crime_recycler_view);
		mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));

		updateUI();

		return view;
	}

	@Override
	public void onResume() {
		super.onResume();
		updateUI();
	}

	public void updateUI() {
		CrimeLab crimeLab = CrimeLab.get(getActivity());
		List<Crime> crimes = crimeLab.getCrimes();

		if (mAdapter == null) {
			mAdapter = new CrimeAdapter(crimes);
			mCrimeRecyclerView.setAdapter(mAdapter);
		} else {
			mAdapter.notifyDataSetChanged();
		}
	}

	private class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
		private Crime mCrime;
		private TextView mTitleTextView;
		private TextView mDateTextView;
		private ImageView mSolvedImageView;

		public CrimeHolder(LayoutInflater inflater, ViewGroup parent) {
			super(inflater.inflate(R.layout.list_item_crime, parent, false));
			itemView.setOnClickListener(this);

			mTitleTextView = (TextView) itemView.findViewById(R.id.crime_title);
			mDateTextView = (TextView) itemView.findViewById(R.id.crime_date);
			mSolvedImageView = (ImageView) itemView.findViewById(R.id.crime_solved);
		}

		public void bind(Crime crime) {
			mCrime = crime;
			mTitleTextView.setText(mCrime.getTitle());
			mDateTextView.setText(mCrime.getDate().toString());
			mSolvedImageView.setVisibility(crime.isSolved() ? View.VISIBLE : View.GONE);
		}

		@Override
		public void onClick(View view) {
			mCallbacks.onCrimeSelected(mCrime);
		}
	}

	private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
		private List<Crime> mCrimes;

		public CrimeAdapter(List<Crime> crimes) {
			mCrimes = crimes;
		}

		@Override
		public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
			LayoutInflater layoutInflater = LayoutInflater.from(getActivity());

			return new CrimeHolder(layoutInflater, parent);
		}

		@Override
		public void onBindViewHolder(CrimeHolder holder, int position) {
			Crime crime = mCrimes.get(position);
			holder.bind(crime);
		}

		@Override
		public int getItemCount() {
			return mCrimes.size();
		}
	}

	@Override
	public void onDetach() {
		super.onDetach();
		mCallbacks = null;
	}
}
CrimePagerActivity.java
package com.bignerdranch.android.criminalintent;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;

import java.util.List;
import java.util.UUID;

/**
 * Created by behin on 8/17/2017.
 */

public class CrimePagerActivity extends AppCompatActivity
		implements CrimeFragment.Callbacks{
	private static final String EXTRA_CRIME_ID = "com.bignerdranch.android.criminalintent.crime_id";

	private ViewPager mViewPager;
	private List<Crime> mCrimes;

	public static Intent newIntent(Context packageContext, UUID crimeId) {
		Intent intent = new Intent(packageContext, CrimePagerActivity.class);
		intent.putExtra(EXTRA_CRIME_ID, crimeId);
		return intent;
	}

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

		UUID crimeId = (UUID) getIntent().getSerializableExtra(EXTRA_CRIME_ID);

		mViewPager = (ViewPager) findViewById(R.id.crime_view_pager);

		mCrimes = CrimeLab.get(this).getCrimes();
		FragmentManager fragmentManager = getSupportFragmentManager();
		mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) {
			@Override
			public Fragment getItem(int position) {
				Crime crime = mCrimes.get(position);
				return CrimeFragment.newInstance(crime.getId());
			}

			@Override
			public int getCount() {
				return mCrimes.size();
			}
		});

		for (int i = 0; i < mCrimes.size(); i++) {
			if (mCrimes.get(i).getId().equals(crimeId)) {
				mViewPager.setCurrentItem(i);
				break;
			}
		}
	}

	@Override
	public void onCrimeUpdated(Crime crime) {

	}
}
DatePickerFragment.java
package com.bignerdranch.android.criminalintent;


import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.DatePicker;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

/**
 * Created by behin on 8/21/2017.
 */

public class DatePickerFragment extends DialogFragment {
	public static final String EXTRA_DATE = "com.bignerdranch.android.criminalintent.date";
	private static final String ARG_DATE = "date";

	private DatePicker mDatePicker;
	private Button mDatePickerOkButton;

	public static DatePickerFragment newInstance(Date date) {
		Bundle arg = new Bundle();
		arg.putSerializable(ARG_DATE, date);

		DatePickerFragment fragment = new DatePickerFragment();
		fragment.setArguments(arg);

		return fragment;
	}


	@Nullable
	@Override
	public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
		Date date = (Date) getArguments().getSerializable(ARG_DATE);

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		int year = calendar.get(Calendar.YEAR);
		int month = calendar.get(Calendar.MONTH);
		int day = calendar.get(Calendar.DAY_OF_MONTH);

		View v = inflater.inflate(R.layout.dialog_date, container, false);

		mDatePicker = (DatePicker) v.findViewById(R.id.dialog_date_picker);
		mDatePicker.init(year, month, day, null);

		mDatePickerOkButton = (Button) v.findViewById(R.id.dialog_date_ok_button);
		mDatePickerOkButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View view) {
				int year = mDatePicker.getYear();
				int month = mDatePicker.getMonth();
				int day = mDatePicker.getDayOfMonth();
				Date date = new GregorianCalendar(year, month, day).getTime();
				sendResult(Activity.RESULT_OK, date);
			}
		});

		return v;

	}

	private void sendResult(int resultCode, Date date){
		Intent data = new Intent();
		data.putExtra(EXTRA_DATE, date);

		if (getTargetFragment() == null) {
			Activity hostingActivity = getActivity();
			hostingActivity.setResult(resultCode, data);
			hostingActivity.finish();
		} else {
			dismiss();
			getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, data);
		}
	}
}
layout/activity_twopane.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
			  android:orientation="horizontal"
			  android:layout_width="match_parent"
			  android:layout_height="match_parent"
			  android:divider="?android:attr/dividerHorizontal"
			  android:showDividers="middle">
	<FrameLayout
		android:id="@+id/fragment_container"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_weight="1"></FrameLayout>
	<FrameLayout
		android:id="@+id/detail_fragment_container"
		android:layout_width="0dp"
		android:layout_height="match_parent"
		android:layout_weight="3"></FrameLayout>

</LinearLayout>
layout/dialog_date.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
											 xmlns:app="http://schemas.android.com/apk/res-auto"
											 xmlns:tools="http://schemas.android.com/tools"
											 android:layout_width="wrap_content"
											 android:layout_height="wrap_content"
											 android:orientation="vertical"
											 tools:layout_editor_absoluteX="0dp"
											 tools:layout_editor_absoluteY="81dp">

	<DatePicker
		android:id="@+id/dialog_date_picker"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginLeft="8dp"
		android:layout_marginRight="8dp"
		android:layout_marginStart="18dp"
		android:layout_marginTop="24dp"
		android:calendarViewShown="false"
		app:layout_constraintHorizontal_bias="0.5"
		app:layout_constraintLeft_toLeftOf="parent"
		app:layout_constraintRight_toRightOf="parent"
		app:layout_constraintTop_toTopOf="parent">

	</DatePicker>

	<Button
		android:id="@+id/dialog_date_ok_button"
		style="?attr/buttonBarPositiveButtonStyle"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginBottom="16dp"
		android:layout_marginEnd="16dp"
		android:layout_marginRight="16dp"
		android:layout_marginTop="16dp"
		android:text="@android:string/ok"
		app:layout_constraintBottom_toBottomOf="parent"
		app:layout_constraintRight_toRightOf="parent"
		app:layout_constraintTop_toBottomOf="@+id/dialog_date_picker"/>

</android.support.constraint.ConstraintLayout>
values/bools.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<bool name="large_layout">false</bool>
</resources>
values-sw600dp/bools.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<bool name="large_layout">true</bool>
</resources>
values/refs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<item name="activity_masterdetail" type="layout">@layout/activity_fragment</item>
</resources>
values-sw600dp/refs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<item name="activity_masterdetail" type="layout">@layout/activity_twopane</item>
</resources>

#2

Been able to follow solution pretty well. However, your CrimeFragment.java file mentions a DatePickerActivity in the line:
Intent intent = DatePickerActivity.newIntent(getContext(), mCrime.getDate());
which I don’t see in your list of files. Was this uploaded?

Thanks


#3

Hi,
I’m sorry, I missed that.
Please find the code below:

DatePickerActivity.java
package com.bignerdranch.android.criminalintent;

import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;

import java.util.Date;

/**
 * Created by behin on 8/22/2017.
 */

public class DatePickerActivity extends SingleFragmentActivity {
	private static final String EXTRA_CRIME_DATE =
			"com.bignerdranch.android.criminalintent.crime_date";

	public static Intent newIntent(Context packageContext, Date date) {
		Intent intent = new Intent(packageContext, DatePickerActivity.class);
		intent.putExtra(EXTRA_CRIME_DATE, date);
		return intent;
	}

	@Override
	protected Fragment createFragment() {
		Date date = (Date) getIntent().getSerializableExtra(EXTRA_CRIME_DATE);
		return DatePickerFragment.newInstance(date);
	}
}

#4

Thanks! That helped solve my issue :smile:


#5

@BC2017, In your DatePickerActivity.java, method newIntent, I think you should try to avoid putting an object (Date object) into Intent. Instead, I extract “year, month, day”, put them in an array, and pass into Intent.


#6

Hi,
Honestly, I don’t know what is the best practice, but I think it is better to use Date Object since it implements Serializable. So, the date will kept safe and accurate. Also, we will save some times of writing extra codes.

Thanks.


#7

After more researching, I think you are right. Date Object is serializable so what I did wasn’t necessary. Thanks for pointing out.