Skip to content

Instantly share code, notes, and snippets.

@ehbc221
Last active June 8, 2023 16:56
Show Gist options
  • Select an option

  • Save ehbc221/916b4dff95d71c758cd7bc481b7b1be8 to your computer and use it in GitHub Desktop.

Select an option

Save ehbc221/916b4dff95d71c758cd7bc481b7b1be8 to your computer and use it in GitHub Desktop.
A series of useful filters to handle searching via APIs in Flutter. Inspired from Spring boot backend ones.
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
import 'package:expenza/network/service/filter/filter.dart';
/// Filter a [bool].
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class BoolFilter extends Filter<bool>
with EquatableMixin, ToJsonFieldMixin<Filter<bool>> {
const BoolFilter({
bool? equals,
bool? notEquals,
bool? specified,
List<bool>? inn,
List<bool>? notIn,
}) : super(
equals: equals,
notEquals: notEquals,
specified: specified,
inn: inn,
notIn: notIn,
);
factory BoolFilter.fromFilter(BoolFilter filter) => BoolFilter(
equals: filter.equals,
notEquals: filter.notEquals,
specified: filter.specified,
inn: filter.inn,
notIn: filter.notIn,
);
factory BoolFilter.fromJson(JsonMap json) => BoolFilter(
equals: json['equals'] as bool?,
notEquals: json['notEquals'] as bool?,
specified: json['specified'] as bool?,
inn: (json['in'] as List<dynamic>?)
?.map((dynamic e) => e as bool)
.toList(),
notIn: (json['notIn'] as List<dynamic>?)
?.map((dynamic e) => e as bool)
.toList(),
);
@override
BoolFilter copy() => BoolFilter.fromFilter(this);
@override
BoolFilter copyWith({
bool? equals,
bool? notEquals,
bool? specified,
List<bool>? inn,
List<bool>? notIn,
}) =>
BoolFilter(
equals: equals ?? this.equals,
notEquals: notEquals ?? this.notEquals,
specified: specified ?? this.specified,
inn: inn ?? this.inn,
notIn: notIn ?? this.notIn,
);
}
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
/// Filter data.
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class Filter<FIELD_TYPE>
with EquatableMixin, ToJsonFieldMixin<Filter<FIELD_TYPE>> {
const Filter({
this.equals,
this.notEquals,
this.specified,
this.inn,
this.notIn,
});
factory Filter.fromFilter(Filter<FIELD_TYPE> filter) => Filter<FIELD_TYPE>(
equals: filter.equals,
notEquals: filter.notEquals,
specified: filter.specified,
inn: filter.inn,
notIn: filter.notIn,
);
final FIELD_TYPE? equals;
final FIELD_TYPE? notEquals;
final bool? specified;
final List<FIELD_TYPE>? inn;
final List<FIELD_TYPE>? notIn;
@override
List<Object?> get props => <Object?>[
equals,
notEquals,
specified,
inn,
notIn,
];
Filter<FIELD_TYPE> copy() => Filter<FIELD_TYPE>.fromFilter(this);
Filter<FIELD_TYPE> copyWith({
FIELD_TYPE? equals,
FIELD_TYPE? notEquals,
bool? specified,
List<FIELD_TYPE>? inn,
List<FIELD_TYPE>? notIn,
}) =>
Filter<FIELD_TYPE>(
equals: equals ?? this.equals,
notEquals: notEquals ?? this.notEquals,
specified: specified ?? this.specified,
inn: inn ?? this.inn,
notIn: notIn ?? this.notIn,
);
/// Convert this to a json field.
@override
JsonMap toJsonField(String fieldName) {
JsonMap json = <String, dynamic>{};
if (equals != null) {
json.addAll(<String, dynamic>{'$fieldName.equals': equals!});
}
if (notEquals != null) {
json.addAll(<String, dynamic>{'$fieldName.notEquals': notEquals!});
}
if (specified != null) {
json.addAll(<String, dynamic>{'$fieldName.specified': specified!});
}
if (inn != null) {
json.addAll(<String, dynamic>{'$fieldName.in': inn!});
}
if (notIn != null) {
json.addAll(<String, dynamic>{'$fieldName.notIn': notIn!});
}
return json;
}
}
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
import 'package:expenza/network/service/filter/filter.dart';
import 'package:expenza/network/service/filter/range.filter.dart';
/// Filter a [int].
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class IntFilter extends RangeFilter<num>
with EquatableMixin, ToJsonFieldMixin<Filter<num>> {
const IntFilter({
num? equals,
num? notEquals,
bool? specified,
List<num>? inn,
List<num>? notIn,
num? greaterThan,
num? lessThan,
num? greaterThanOrEqual,
num? lessThanOrEqual,
}) : super(
equals: equals,
notEquals: notEquals,
specified: specified,
inn: inn,
notIn: notIn,
greaterThan: greaterThan,
lessThan: lessThan,
greaterThanOrEqual: greaterThanOrEqual,
lessThanOrEqual: lessThanOrEqual,
);
factory IntFilter.fromFilter(IntFilter filter) => IntFilter(
equals: filter.equals,
notEquals: filter.notEquals,
specified: filter.specified,
inn: filter.inn,
notIn: filter.notIn,
greaterThan: filter.greaterThan,
lessThan: filter.lessThan,
greaterThanOrEqual: filter.greaterThanOrEqual,
lessThanOrEqual: filter.lessThanOrEqual,
);
factory IntFilter.fromJson(JsonMap json) => IntFilter(
equals: json['equals'] as num?,
notEquals: json['notEquals'] as num?,
specified: json['specified'] as bool?,
inn: (json['in'] as List<dynamic>?)
?.map((dynamic e) => e as num)
.toList(),
notIn: (json['notIn'] as List<dynamic>?)
?.map((dynamic e) => e as num)
.toList(),
greaterThan: json['greaterThan'] as num?,
lessThan: json['lessThan'] as num?,
greaterThanOrEqual: json['greaterThanOrEqual'] as num?,
lessThanOrEqual: json['lessThanOrEqual'] as num?,
);
@override
IntFilter copy() => IntFilter.fromFilter(this);
@override
IntFilter copyWith({
num? equals,
num? notEquals,
bool? specified,
List<num>? inn,
List<num>? notIn,
num? greaterThan,
num? lessThan,
num? greaterThanOrEqual,
num? lessThanOrEqual,
}) =>
IntFilter(
equals: equals ?? this.equals,
notEquals: notEquals ?? this.notEquals,
specified: specified ?? this.specified,
inn: inn ?? this.inn,
notIn: notIn ?? this.notIn,
greaterThan: greaterThan ?? this.greaterThan,
lessThan: lessThan ?? this.lessThan,
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual,
);
}
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/enum/period.enum.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
import 'package:expenza/network/service/filter/index.dart';
import 'package:json_annotation/json_annotation.dart';
/// Filter for [Period].
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class PeriodFilter extends Filter<Period>
with EquatableMixin, ToJsonFieldMixin<Filter<Period>> {
const PeriodFilter({
Period? equals,
Period? notEquals,
bool? specified,
List<Period>? inn,
List<Period>? notIn,
}) : super(
equals: equals,
notEquals: notEquals,
specified: specified,
inn: inn,
notIn: notIn,
);
factory PeriodFilter.fromFilter(PeriodFilter filter) =>
Filter<Period>.fromFilter(filter) as PeriodFilter;
factory PeriodFilter.fromJson(JsonMap json) => PeriodFilter(
equals: $enumDecodeNullable($PeriodEnumMap, json['equals']),
notEquals: $enumDecodeNullable($PeriodEnumMap, json['notEquals']),
specified: json['specified'] as bool?,
inn: (json['in'] as List<dynamic>?)
?.map(
(dynamic e) => $enumDecodeNullable($PeriodEnumMap, json['in'])!,
)
.toList(),
notIn: (json['notIn'] as List<dynamic>?)
?.map(
(dynamic e) =>
$enumDecodeNullable($PeriodEnumMap, json['notIn'])!,
)
.toList(),
);
@override
List<Object?> get props => <Object?>[
equals,
notEquals,
specified,
inn,
notIn,
];
@override
PeriodFilter copy() => PeriodFilter.fromFilter(this);
@override
JsonMap toJsonField(String fieldName) {
JsonMap json = super.toJsonField(fieldName);
if (equals != null) {
json.addAll(
<String, dynamic>{'$fieldName.equals': equals!.name.toUpperCase()},
);
}
if (notEquals != null) {
json.addAll(<String, dynamic>{
'$fieldName.notEquals': notEquals!.name.toUpperCase()
});
}
if (specified != null) {
json.addAll(<String, dynamic>{'$fieldName.specified': specified!});
}
if (inn != null) {
json.addAll(<String, dynamic>{
'$fieldName.in': inn!
.map(
(Period period) => period.name.toUpperCase(),
)
.toList()
});
}
if (notIn != null) {
json.addAll(<String, dynamic>{
'$fieldName.notIn': notIn!
.map(
(Period period) => period.name.toUpperCase(),
)
.toList()
});
}
return json;
}
}
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
import 'package:expenza/network/service/filter/filter.dart';
/// Filter data in range.
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class RangeFilter<FIELD_TYPE extends Comparable<FIELD_TYPE>>
extends Filter<FIELD_TYPE>
with EquatableMixin, ToJsonFieldMixin<Filter<FIELD_TYPE>> {
const RangeFilter({
FIELD_TYPE? equals,
FIELD_TYPE? notEquals,
bool? specified,
List<FIELD_TYPE>? inn,
List<FIELD_TYPE>? notIn,
this.greaterThan,
this.lessThan,
this.greaterThanOrEqual,
this.lessThanOrEqual,
}) : super(
equals: equals,
notEquals: notEquals,
specified: specified,
inn: inn,
notIn: notIn,
);
factory RangeFilter.fromFilter(RangeFilter<FIELD_TYPE> filter) =>
RangeFilter<FIELD_TYPE>(
equals: filter.equals,
notEquals: filter.notEquals,
specified: filter.specified,
inn: filter.inn,
notIn: filter.notIn,
greaterThan: filter.greaterThan,
lessThan: filter.lessThan,
greaterThanOrEqual: filter.greaterThanOrEqual,
lessThanOrEqual: filter.lessThanOrEqual,
);
final FIELD_TYPE? greaterThan;
final FIELD_TYPE? lessThan;
final FIELD_TYPE? greaterThanOrEqual;
final FIELD_TYPE? lessThanOrEqual;
@override
List<Object?> get props => <Object?>[
greaterThan,
lessThan,
greaterThanOrEqual,
lessThanOrEqual,
];
@override
RangeFilter<FIELD_TYPE> copy() => RangeFilter<FIELD_TYPE>.fromFilter(this);
RangeFilter<FIELD_TYPE> copyWithRangeFilter({
FIELD_TYPE? equals,
FIELD_TYPE? notEquals,
bool? specified,
List<FIELD_TYPE>? inn,
List<FIELD_TYPE>? notIn,
FIELD_TYPE? greaterThan,
FIELD_TYPE? lessThan,
FIELD_TYPE? greaterThanOrEqual,
FIELD_TYPE? lessThanOrEqual,
}) =>
RangeFilter<FIELD_TYPE>(
equals: equals ?? this.equals,
notEquals: notEquals ?? this.notEquals,
specified: specified ?? this.specified,
inn: inn ?? this.inn,
notIn: notIn ?? this.notIn,
greaterThan: greaterThan ?? this.greaterThan,
lessThan: lessThan ?? this.lessThan,
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual,
);
@override
JsonMap toJsonField(String fieldName) {
JsonMap json = super.toJsonField(fieldName);
if (greaterThan != null) {
json.addAll(<String, dynamic>{'$fieldName.greaterThan': greaterThan!});
}
if (lessThan != null) {
json.addAll(
<String, dynamic>{'$fieldName.lessThan': lessThan!},
);
}
if (greaterThanOrEqual != null) {
json.addAll(<String, dynamic>{
'$fieldName.greaterThanOrEqual': greaterThanOrEqual!
});
}
if (lessThanOrEqual != null) {
json.addAll(
<String, dynamic>{'$fieldName.lessThanOrEqual': lessThanOrEqual!},
);
}
return json;
}
}
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
import 'package:expenza/network/service/filter/filter.dart';
/// Filter a [String].
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class StringFilter extends Filter<String>
with EquatableMixin, ToJsonFieldMixin<Filter<String>> {
const StringFilter({
String? equals,
String? notEquals,
bool? specified,
List<String>? inn,
List<String>? notIn,
this.contains,
this.doesNotContain,
}) : super(
equals: equals,
notEquals: notEquals,
specified: specified,
inn: inn,
notIn: notIn,
);
factory StringFilter.fromFilter(StringFilter filter) => StringFilter(
equals: filter.equals,
notEquals: filter.notEquals,
specified: filter.specified,
inn: filter.inn,
notIn: filter.notIn,
contains: filter.contains,
doesNotContain: filter.doesNotContain,
);
factory StringFilter.fromJson(JsonMap json) => StringFilter(
equals: json['equals'] as String?,
notEquals: json['notEquals'] as String?,
specified: json['specified'] as bool?,
inn: (json['in'] as List<dynamic>?)
?.map((dynamic e) => e as String)
.toList(),
notIn: (json['notIn'] as List<dynamic>?)
?.map((dynamic e) => e as String)
.toList(),
contains: json['contains'] as String?,
doesNotContain: json['doesNotContain'] as String?,
);
final String? contains;
final String? doesNotContain;
@override
List<Object?> get props => <Object?>[
equals,
notEquals,
specified,
inn,
notIn,
contains,
doesNotContain,
];
@override
StringFilter copy() => StringFilter.fromFilter(this);
@override
StringFilter copyWith({
String? equals,
String? notEquals,
bool? specified,
List<String>? inn,
List<String>? notIn,
}) =>
StringFilter(
equals: equals ?? this.equals,
notEquals: notEquals ?? this.notEquals,
specified: specified ?? this.specified,
inn: inn ?? this.inn,
notIn: notIn ?? this.notIn,
);
@override
JsonMap toJsonField(String fieldName) {
JsonMap json = super.toJsonField(fieldName);
if (contains != null) {
json.addAll(<String, dynamic>{'$fieldName.contains': contains!});
}
if (doesNotContain != null) {
json.addAll(
<String, dynamic>{'$fieldName.doesNotContain': doesNotContain!},
);
}
return json;
}
}
import 'package:equatable/equatable.dart';
import 'package:expenza/domain/models/type/json_map.type.dart';
import 'package:expenza/mixins/to_json_field.mixin.dart';
import 'package:expenza/network/service/filter/filter.dart';
import 'package:expenza/network/service/filter/range.filter.dart';
import 'package:expenza/utils/date.utils.dart';
/// Filter a [DateTime].
///
/// - author - @ehbc221
/// - version - 1.0.0
/// - last updated - June 8th, 2023
class ZonedDateTimeFilter extends RangeFilter<DateTime>
with EquatableMixin, ToJsonFieldMixin<Filter<DateTime>> {
const ZonedDateTimeFilter({
DateTime? equals,
DateTime? notEquals,
bool? specified,
List<DateTime>? inn,
List<DateTime>? notIn,
DateTime? greaterThan,
DateTime? lessThan,
DateTime? greaterThanOrEqual,
DateTime? lessThanOrEqual,
}) : super(
equals: equals,
notEquals: notEquals,
specified: specified,
inn: inn,
notIn: notIn,
greaterThan: greaterThan,
lessThan: lessThan,
greaterThanOrEqual: greaterThanOrEqual,
lessThanOrEqual: lessThanOrEqual,
);
factory ZonedDateTimeFilter.fromFilter(ZonedDateTimeFilter filter) =>
ZonedDateTimeFilter(
equals: filter.equals,
notEquals: filter.notEquals,
specified: filter.specified,
inn: filter.inn,
notIn: filter.notIn,
greaterThan: filter.greaterThan,
lessThan: filter.lessThan,
greaterThanOrEqual: filter.greaterThanOrEqual,
lessThanOrEqual: filter.lessThanOrEqual,
);
factory ZonedDateTimeFilter.fromJson(JsonMap json) => ZonedDateTimeFilter(
equals: json['equals'] as DateTime?,
notEquals: json['notEquals'] as DateTime?,
specified: json['specified'] as bool?,
inn: (json['inn'] as List<dynamic>?)
?.map((dynamic e) => e as DateTime)
.toList(),
notIn: (json['notIn'] as List<dynamic>?)
?.map((dynamic e) => e as DateTime)
.toList(),
greaterThan: json['greaterThan'] as DateTime?,
lessThan: json['lessThan'] as DateTime?,
greaterThanOrEqual: json['greaterThanOrEqual'] as DateTime?,
lessThanOrEqual: json['lessThanOrEqual'] as DateTime?,
);
@override
ZonedDateTimeFilter copy() => ZonedDateTimeFilter.fromFilter(this);
@override
ZonedDateTimeFilter copyWith({
DateTime? equals,
DateTime? notEquals,
bool? specified,
List<DateTime>? inn,
List<DateTime>? notIn,
DateTime? greaterThan,
DateTime? lessThan,
DateTime? greaterThanOrEqual,
DateTime? lessThanOrEqual,
}) =>
ZonedDateTimeFilter(
equals: equals ?? this.equals,
notEquals: notEquals ?? this.notEquals,
specified: specified ?? this.specified,
inn: inn ?? this.inn,
notIn: notIn ?? this.notIn,
greaterThan: greaterThan ?? this.greaterThan,
lessThan: lessThan ?? this.lessThan,
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
lessThanOrEqual: lessThanOrEqual ?? this.lessThanOrEqual,
);
@override
JsonMap toJsonField(String fieldName) {
JsonMap json = <String, dynamic>{};
if (equals != null) {
json.addAll(<String, dynamic>{
'$fieldName.equals': formatJsonDateTimeForJson(equals!)
});
}
if (notEquals != null) {
json.addAll(<String, dynamic>{
'$fieldName.notEquals': formatJsonDateTimeForJson(notEquals!)
});
}
if (specified != null) {
json.addAll(<String, dynamic>{'$fieldName.specified': specified!});
}
if (inn != null) {
json.addAll(<String, dynamic>{
'$fieldName.inn': List<String?>.generate(
inn!.length,
(int index) => formatJsonDateTimeForJson(inn![index]),
)
});
}
if (notIn != null) {
json.addAll(<String, dynamic>{
'$fieldName.notIn': List<String?>.generate(
notIn!.length,
(int index) => formatJsonDateTimeForJson(notIn![index]),
)
});
}
if (greaterThan != null) {
json.addAll(<String, dynamic>{
'$fieldName.greaterThan': formatJsonDateTimeForJson(greaterThan!)
});
}
if (lessThan != null) {
json.addAll(
<String, dynamic>{
'$fieldName.lessThan': formatJsonDateTimeForJson(lessThan!)
},
);
}
if (greaterThanOrEqual != null) {
json.addAll(<String, dynamic>{
'$fieldName.greaterThanOrEqual':
formatJsonDateTimeForJson(greaterThanOrEqual!)
});
}
if (lessThanOrEqual != null) {
json.addAll(
<String, dynamic>{
'$fieldName.lessThanOrEqual':
formatJsonDateTimeForJson(lessThanOrEqual!)
},
);
}
return json;
}
}
@ehbc221
Copy link
Copy Markdown
Author

ehbc221 commented Jun 8, 2023

Handling Enumerations

The PeriodFilter is an example for handling filters based on an enumeration, assuming that we have the following period.enum.dart:

import 'package:json_annotation/json_annotation.dart';

/// A Period.
@JsonEnum()
enum Period {
  @JsonValue('ONE_TIME')
  oneTime,
  @JsonValue('DAY')
  day,
  @JsonValue('WEEK')
  week,
  @JsonValue('MONTH')
  month,
  @JsonValue('TRIMESTER')
  trimester,
  @JsonValue('SEMESTER')
  semester,
  @JsonValue('YEAR')
  year,
}

const Map<Period, String> $PeriodEnumMap = <Period, String>{
  Period.oneTime: 'ONE_TIME',
  Period.day: 'DAY',
  Period.week: 'WEEK',
  Period.month: 'MONTH',
  Period.trimester: 'TRIMESTER',
  Period.semester: 'SEMESTER',
  Period.year: 'YEAR',
};

@ehbc221
Copy link
Copy Markdown
Author

ehbc221 commented Jun 8, 2023

Example

Assuming that you want to filter in a list of budgets, we'll take the name field as example:

final StringFilter? name;

Now, you can set a user searched String as name.contains = 'Test' and retrieve in your backend a name.contains key that holds Test as value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment