Package structure: moved aggregated statistics.

Part of #963.
This commit is contained in:
Dennis Guse
2022-01-15 20:12:35 +01:00
parent 5053c41bec
commit 1a125ca60d
12 changed files with 24 additions and 21 deletions
@@ -0,0 +1,84 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
public class AggregatedStatistics {
private final Map<String, AggregatedStatistic> dataMap = new HashMap<>();
private final List<AggregatedStatistic> dataList = new ArrayList<>();
public AggregatedStatistics(@NonNull List<Track> tracks) {
for (Track track : tracks) {
aggregate(track);
}
dataList.addAll(dataMap.values());
Collections.sort(dataList, (o1, o2) -> {
if (o1.getCountTracks() == o2.getCountTracks()) {
return o1.getCategory().compareTo(o2.getCategory());
}
return (o1.getCountTracks() < o2.getCountTracks() ? 1 : -1);
});
}
@VisibleForTesting
public void aggregate(@NonNull Track track) {
String category = track.getCategory();
if (dataMap.containsKey(category)) {
dataMap.get(category).add(track.getTrackStatistics());
} else {
dataMap.put(category, new AggregatedStatistic(category, track.getTrackStatistics()));
}
}
public int getCount() {
return dataMap.size();
}
public AggregatedStatistic get(String category) {
return dataMap.get(category);
}
public AggregatedStatistic getItem(int position) {
return dataList.get(position);
}
public static class AggregatedStatistic {
private final String category;
private final TrackStatistics trackStatistics;
private int countTracks = 1;
public AggregatedStatistic(String category, TrackStatistics trackStatistics) {
this.category = category;
this.trackStatistics = trackStatistics;
}
public String getCategory() {
return category;
}
public TrackStatistics getTrackStatistics() {
return trackStatistics;
}
public int getCountTracks() {
return countTracks;
}
void add(TrackStatistics statistics) {
trackStatistics.merge(statistics);
countTracks++;
}
}
}
@@ -0,0 +1,119 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.AbstractActivity;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackSelection;
import de.dennisguse.opentracks.databinding.AggregatedStatsBinding;
public class AggregatedStatisticsActivity extends AbstractActivity implements FilterDialogFragment.FilterDialogListener {
public static final String EXTRA_TRACK_IDS = "track_ids";
static final String STATE_ARE_FILTERS_APPLIED = "areFiltersApplied";
private AggregatedStatsBinding viewBinding;
private AggregatedStatisticsAdapter adapter;
private AggregatedStatisticsModel viewModel;
private final TrackSelection selection = new TrackSelection();
private boolean areFiltersApplied;
private MenuItem filterItem;
private MenuItem clearFilterItem;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding.aggregatedStatsList.setEmptyView(viewBinding.aggregatedStatsEmptyView);
areFiltersApplied = savedInstanceState != null && savedInstanceState.getBoolean(STATE_ARE_FILTERS_APPLIED);
List<Track.Id> trackIds = getIntent().getParcelableArrayListExtra(EXTRA_TRACK_IDS);
if (trackIds != null && !trackIds.isEmpty()) {
trackIds.stream().forEach(selection::addTrackId);
}
viewModel = new ViewModelProvider(this).get(AggregatedStatisticsModel.class);
viewModel.getAggregatedStats(selection).observe(this, aggregatedStatistics -> {
if ((aggregatedStatistics == null || aggregatedStatistics.getCount() == 0) && !selection.isEmpty()) {
viewBinding.aggregatedStatsEmptyView.setText(getString(R.string.aggregated_stats_filter_no_results));
}
if (aggregatedStatistics != null) {
adapter = new AggregatedStatisticsAdapter(this, aggregatedStatistics);
viewBinding.aggregatedStatsList.setAdapter(adapter);
}
adapter.notifyDataSetChanged();
});
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_ARE_FILTERS_APPLIED, areFiltersApplied);
}
@Override
protected View getRootView() {
viewBinding = AggregatedStatsBinding.inflate(getLayoutInflater());
return viewBinding.getRoot();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.aggregated_statistics, menu);
clearFilterItem = menu.findItem(R.id.aggregated_statistics_clear_filter);
filterItem = menu.findItem(R.id.aggregated_statistics_filter);
setMenuVisibility(areFiltersApplied);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.aggregated_statistics_filter) {
ArrayList<FilterDialogFragment.FilterItem> filterItems = new ArrayList<>();
adapter.getCategories().stream().forEach(category -> filterItems.add(new FilterDialogFragment.FilterItem(category, category, true)));
FilterDialogFragment.showDialog(getSupportFragmentManager(), filterItems);
return true;
}
if (item.getItemId() == R.id.aggregated_statistics_clear_filter) {
setMenuVisibility(false);
viewModel.clearSelection();
return true;
}
return super.onOptionsItemSelected(item);
}
private void setMenuVisibility(boolean areFiltersApplied) {
this.areFiltersApplied = areFiltersApplied;
if (clearFilterItem != null && filterItem != null) {
clearFilterItem.setVisible(this.areFiltersApplied);
filterItem.setVisible(!this.areFiltersApplied);
}
}
@Override
public void onFilterDone(ArrayList<FilterDialogFragment.FilterItem> filterItems, LocalDateTime from, LocalDateTime to) {
setMenuVisibility(true);
selection.addDateRange(from.atZone(ZoneId.systemDefault()).toInstant(), to.atZone(ZoneId.systemDefault()).toInstant());
filterItems.stream().filter(fi -> fi.isChecked).forEach(fi -> selection.addCategory(fi.value));
viewModel.updateSelection(selection);
}
}
@@ -0,0 +1,176 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.content.Context;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.settings.PreferencesUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils;
public class AggregatedStatisticsAdapter extends BaseAdapter {
private AggregatedStatistics aggregatedStatistics;
private final Context context;
public AggregatedStatisticsAdapter(Context context, AggregatedStatistics aggregatedStatistics) {
this.context = context;
this.aggregatedStatistics = aggregatedStatistics;
}
@Override
public int getCount() {
if (aggregatedStatistics != null) {
return aggregatedStatistics.getCount();
}
return 0;
}
@Override
public AggregatedStatistics.AggregatedStatistic getItem(int position) {
return aggregatedStatistics.getItem(position);
}
@Override
public long getItemId(int position) {
// Not important because data are not database register but cooked data.
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
AggregatedStatistics.AggregatedStatistic aggregatedStatistic = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.aggregated_stats_list_item, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if (TrackIconUtils.isSpeedIcon(context.getResources(), aggregatedStatistic.getCategory())) {
viewHolder.setSpeed(aggregatedStatistic);
} else {
viewHolder.setPace(aggregatedStatistic);
}
return convertView;
}
public AggregatedStatistics swapData(AggregatedStatistics aggregatedStatistics) {
this.aggregatedStatistics = aggregatedStatistics;
this.notifyDataSetChanged();
return aggregatedStatistics;
}
public List<String> getCategories() {
List<String> categories = new ArrayList<>();
for (int i = 0; i < aggregatedStatistics.getCount(); i++) {
categories.add(aggregatedStatistics.getItem(i).getCategory());
}
return categories;
}
private class ViewHolder {
private final ImageView sportIcon;
private final TextView typeLabel;
private final TextView numTracks;
private final TextView distance;
private final TextView distanceUnit;
private final TextView time;
private final TextView avgSpeed;
private final TextView avgSpeedUnit;
private final TextView avgSpeedLabel;
private final TextView maxSpeed;
private final TextView maxSpeedUnit;
private final TextView maxSpeedLabel;
private boolean metricsUnits;
private boolean reportSpeed;
public ViewHolder(View view) {
sportIcon = view.findViewById(R.id.aggregated_stats_sport_icon);
typeLabel = view.findViewById(R.id.aggregated_stats_type_label);
numTracks = view.findViewById(R.id.aggregated_stats_num_tracks);
distance = view.findViewById(R.id.aggregated_stats_distance);
distanceUnit = view.findViewById(R.id.aggregated_stats_distance_unit);
time = view.findViewById(R.id.aggregated_stats_time);
avgSpeed = view.findViewById(R.id.aggregated_stats_avg_rate);
avgSpeedUnit = view.findViewById(R.id.aggregated_stats_avg_rate_unit);
avgSpeedLabel = view.findViewById(R.id.aggregated_stats_avg_rate_label);
maxSpeed = view.findViewById(R.id.aggregated_stats_max_rate);
maxSpeedUnit = view.findViewById(R.id.aggregated_stats_max_rate_unit);
maxSpeedLabel = view.findViewById(R.id.aggregated_stats_max_rate_label);
}
public void setSpeed(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
setCommonValues(aggregatedStatistic);
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
avgSpeed.setText(parts.first);
avgSpeedUnit.setText(parts.second);
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_speed));
}
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
maxSpeed.setText(parts.first);
maxSpeedUnit.setText(parts.second);
maxSpeedLabel.setText(context.getString(R.string.stats_max_speed));
}
}
public void setPace(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
setCommonValues(aggregatedStatistic);
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getAverageMovingSpeed(), metricsUnits, reportSpeed);
avgSpeed.setText(parts.first);
avgSpeedUnit.setText(parts.second);
avgSpeedLabel.setText(context.getString(R.string.stats_average_moving_pace));
}
{
Pair<String, String> parts = StringUtils.getSpeedParts(context, aggregatedStatistic.getTrackStatistics().getMaxSpeed(), metricsUnits, reportSpeed);
maxSpeed.setText(parts.first);
maxSpeedUnit.setText(parts.second);
maxSpeedLabel.setText(R.string.stats_fastest_pace);
}
}
//TODO Check preference handling.
private void setCommonValues(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String category = aggregatedStatistic.getCategory();
reportSpeed = PreferencesUtils.isReportSpeed(category);
metricsUnits = PreferencesUtils.isMetricUnits();
sportIcon.setImageResource(getIcon(aggregatedStatistic));
typeLabel.setText(category);
numTracks.setText(StringUtils.valueInParentheses(String.valueOf(aggregatedStatistic.getCountTracks())));
Pair<String, String> parts = StringUtils.getDistanceParts(context, aggregatedStatistic.getTrackStatistics().getTotalDistance(), metricsUnits);
distance.setText(parts.first);
distanceUnit.setText(parts.second);
time.setText(StringUtils.formatElapsedTime(aggregatedStatistic.getTrackStatistics().getMovingTime()));
}
private int getIcon(AggregatedStatistics.AggregatedStatistic aggregatedStatistic) {
String iconValue = TrackIconUtils.getIconValue(context, aggregatedStatistic.getCategory());
return TrackIconUtils.getIconDrawable(iconValue);
}
}
}
@@ -0,0 +1,51 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackSelection;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
public class AggregatedStatisticsModel extends AndroidViewModel {
private MutableLiveData<AggregatedStatistics> aggregatedStats;
public AggregatedStatisticsModel(@NonNull Application application) {
super(application);
}
public LiveData<AggregatedStatistics> getAggregatedStats(@Nullable TrackSelection selection) {
if (aggregatedStats == null) {
aggregatedStats = new MutableLiveData<>();
loadAggregatedStats(selection);
}
return aggregatedStats;
}
public void updateSelection(TrackSelection selection) {
loadAggregatedStats(selection);
}
public void clearSelection() {
loadAggregatedStats(new TrackSelection());
}
private void loadAggregatedStats(TrackSelection selection) {
new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(getApplication().getApplicationContext());
List<Track> tracks = selection != null ? contentProviderUtils.getTracks(selection) : contentProviderUtils.getTracks();
AggregatedStatistics aggregatedStatistics = new AggregatedStatistics(tracks);
aggregatedStats.postValue(aggregatedStatistics);
}).start();
}
}
@@ -0,0 +1,91 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
*
* 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 de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.DialogUtils;
/**
* A DialogFragment to confirm delete.
*
* @author Jimmy Shih
*/
public class ConfirmDeleteDialogFragment extends DialogFragment {
private static final String CONFIRM_DELETE_DIALOG_TAG = "confirmDeleteDialog";
private static final String KEY_TRACK_IDS = "trackIds";
private ConfirmDeleteCaller caller;
/**
* Create a new instance.
*
* @param trackIds list of track ids to delete.
*/
public static void showDialog(FragmentManager fragmentManager, Track.Id... trackIds) {
Bundle bundle = new Bundle();
bundle.putParcelableArray(KEY_TRACK_IDS, trackIds);
ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment();
deleteTrackDialogFragment.setArguments(bundle);
deleteTrackDialogFragment.show(fragmentManager, CONFIRM_DELETE_DIALOG_TAG);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
caller = (ConfirmDeleteCaller) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + ConfirmDeleteCaller.class.getSimpleName());
}
}
@Override
@NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Track.Id[] trackIds = (Track.Id[]) getArguments().getParcelableArray(KEY_TRACK_IDS);
int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
return DialogUtils.createConfirmationDialog(
getActivity(),
titleId,
getString(messageId),
(dialog, which) -> caller.onConfirmDeleteDone(trackIds)
);
}
/**
* Interface for caller of this dialog fragment.
*
* @author Jimmy Shih
*/
public interface ConfirmDeleteCaller {
void onConfirmDeleteDone(Track.Id... trackIds);
}
}
@@ -0,0 +1,186 @@
package de.dennisguse.opentracks.ui.aggregatedStatistics;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.GridLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.textfield.TextInputEditText;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Locale;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.util.StringUtils;
public class FilterDialogFragment extends DialogFragment {
private static final String TAG = FilterDialogFragment.class.getSimpleName();
public static final String KEY_FILTER_ITEMS = "filterItems";
private FilterDialogListener filterDialogListener;
private ArrayList<FilterItem> filterItems = new ArrayList<>();
public static void showDialog(FragmentManager fragmentManager) {
FilterDialogFragment filterDialogFragment = new FilterDialogFragment();
filterDialogFragment.show(fragmentManager, TAG);
}
public static void showDialog(FragmentManager fragmentManager, ArrayList<FilterItem> items) {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(KEY_FILTER_ITEMS, items);
FilterDialogFragment filterDialogFragment = new FilterDialogFragment();
filterDialogFragment.setArguments(bundle);
filterDialogFragment.show(fragmentManager, TAG);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
filterItems = getArguments().getParcelableArrayList(KEY_FILTER_ITEMS);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getString(R.string.generic_filter));
LayoutInflater inflater = LayoutInflater.from(getActivity());
View layout = inflater.inflate(R.layout.fragment_filter_dialog, null, false);
GridLayout itemsLayout = layout.findViewById(R.id.filter_items);
builder.setView(layout);
for (FilterItem item : filterItems) {
View view = inflater.inflate(R.layout.fragment_filter_dialog_item, null);
CheckBox checkBox = view.findViewById(R.id.filter_dialog_check_button);
checkBox.setText(item.value);
checkBox.setChecked(item.isChecked);
checkBox.setTag(item.id);
checkBox.setOnClickListener(v -> item.isChecked = !item.isChecked);
itemsLayout.addView(view);
}
DatePicker datePickerFrom = layout.findViewById(R.id.filter_date_picker_from);
DatePicker datePickerTo = layout.findViewById(R.id.filter_date_picker_to);
TextInputEditText dateFrom = layout.findViewById(R.id.filter_date_edit_text_from);
TextInputEditText dateTo = layout.findViewById(R.id.filter_date_edit_text_to);
LocalDateTime firstDayThisWeek = LocalDate.now().with(WeekFields.of(Locale.getDefault()).getFirstDayOfWeek()).atStartOfDay();
dateFrom.setText(StringUtils.formatLocalDateTime(firstDayThisWeek));
datePickerFrom.init(firstDayThisWeek.getYear(), firstDayThisWeek.getMonthValue() - 1, firstDayThisWeek.getDayOfMonth(), (view, year, monthOfYear, dayOfMonth) -> {
LocalDateTime localDateTime = LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 0, 0, 0);
dateFrom.setText(StringUtils.formatLocalDateTime(localDateTime));
datePickerFrom.setVisibility(View.GONE);
datePickerTo.setMinDate(localDateTime.toInstant(ZoneOffset.ofTotalSeconds(0)).toEpochMilli());
if (localDateTime.isAfter(LocalDateTime.of(datePickerTo.getYear(), datePickerTo.getMonth() + 1, datePickerTo.getDayOfMonth(), 23, 59, 59))) {
datePickerTo.updateDate(year, monthOfYear, dayOfMonth);
}
});
LocalDateTime lastDayThisWeek = firstDayThisWeek.plusDays(6).withHour(23).withMinute(59).withSecond(59);
dateTo.setText(StringUtils.formatLocalDateTime(lastDayThisWeek));
datePickerTo.init(lastDayThisWeek.getYear(), lastDayThisWeek.getMonthValue() - 1, lastDayThisWeek.getDayOfMonth(), (view, year, monthOfYear, dayOfMonth) -> {
LocalDateTime localDateTime = LocalDateTime.of(year, monthOfYear + 1, dayOfMonth, 23, 59, 59);
dateTo.setText(StringUtils.formatLocalDateTime(localDateTime));
datePickerTo.setVisibility(View.GONE);
});
dateFrom.setOnClickListener(v -> {
datePickerFrom.setVisibility(View.VISIBLE);
datePickerTo.setVisibility(View.GONE);
});
dateTo.setOnClickListener(v -> {
datePickerFrom.setVisibility(View.GONE);
datePickerTo.setVisibility(View.VISIBLE);
});
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> filterDialogListener.onFilterDone(
filterItems,
LocalDateTime.of(datePickerFrom.getYear(), datePickerFrom.getMonth() + 1, datePickerFrom.getDayOfMonth(), 0, 0, 0),
LocalDateTime.of(datePickerTo.getYear(), datePickerTo.getMonth() + 1, datePickerTo.getDayOfMonth(), 23, 59, 59)
));
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
filterDialogListener = (FilterDialogListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context + " must implement " + FilterDialogListener.class.getSimpleName());
}
}
public interface FilterDialogListener {
void onFilterDone(ArrayList<FilterItem> filters, LocalDateTime from, LocalDateTime to);
}
public static class FilterItem implements Parcelable {
public final String id;
public final String value;
public boolean isChecked;
public FilterItem(String id, String value) {
this.id = id;
this.value = value;
this.isChecked = true;
}
public FilterItem(String id, String value, boolean isChecked) {
this.id = id;
this.value = value;
this.isChecked = isChecked;
}
protected FilterItem(Parcel in) {
id = in.readString();
value = in.readString();
isChecked = in.readByte() != 0;
}
public static final Creator<FilterItem> CREATOR = new Creator<>() {
@Override
public FilterItem createFromParcel(Parcel in) {
return new FilterItem(in);
}
@Override
public FilterItem[] newArray(int size) {
return new FilterItem[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(value);
dest.writeByte((byte) (isChecked ? 1 : 0));
}
}
}