Hayiqu Starter Template
flutter pub add hayiqu
Hayiqu includes structures that you will use in most of your projects. We recommend using Hayiqu instead of writing the same codes over and over again.
What does Hayiqu include?
-
Provider for State Management and Dependency Injection with
GetItpackage along withBaseViewModelstructure, -
GoRouterpackage for navigation, -
DeviceInfoUtils,DeviceOrientationSettings, andPackageInfoUtilspackages to control the device, -
Different extensions for
Context,DateTime,Number, andString, -
Skeletonizer widget structure for loading screens,
-
A customized and easy-to-use
HttpServicestructure based on Dio package, -
BaseThemestructure for application theme, -
gap,flutter_svg,cached_network_image,toastification,modal_bottom_sheetpackage, andfluid dialogwidget structure for UI, -
flutter_cache_managerpackage for cache management, -
cryptopackage for encryption,
Usage Examples
You can extend the BaseViewModel structure to add convenient features to the ViewModel structure created with Provider.
// Example usage
class MyViewModel with BaseViewModel {
final _apiService = getIt<ApiRepository>();
List<Todo> _todos = [];
List<Todo> get todos => _todos;
HomeViewmodel() {
runBusyFuture(_getTodos());
}
Future<void> _getTodos() async {
_todos = await _apiService.getTodos();
}
}
You can use the GetIt package for Dependency Injection.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// HttpService is registered on GetIt by default.
await setupLocator(() {
// Register your GetIt service here
getIt.registerLazySingleton<ApiRepository>(() => ApiService());
});
runApp(const MyApp());
}
To get detailed information about the current device;
print(await DeviceInfoUtils().getPlatformName());
print(await DeviceInfoUtils().getDeviceId());
For device-oriented screens;
await DeviceOrientationSettings.screenVertical();
await DeviceOrientationSettings.screenHorizontal();
await DeviceOrientationSettings.screenRight();
await DeviceOrientationSettings.screenLeft();
To get information about the application;
await PackageInfoUtils().init();
print(PackageInfoUtils().getAppVersion());
print(PackageInfoUtils().getAppName());
For loading (waiting) screens with content;
Skeletonizer(
enabled: true,
...
child: const HomeScreen(),
);
To customize or use HttpService;
class ApiService {
final _httpService = getIt<HttpService>();
void config() {
_httpService.enableLogger(true);
_httpService.setBaseUrl("example.com");
_httpService.setHeaders({});
_httpService.dio.options.connectTimeout = const Duration(seconds: 5);
_httpService.dio.interceptors.add();
}
// GET request (with cache)
void _exampleGetRequest() async {
final Result result = await _httpService.get("/users", useCache: true);
if (result.hasValue) {}
if (result.hasError) {
result.requireError.message.log();
}
}
// POST request
void _examplePostRequest() async {
final Map<String, dynamic> data = {"name": "John Doe"};
final Result result = await _httpService.post("/users", data: data);
if (result.hasValue) {}
if (result.hasError) {
result.requireError.message.log();
}
}
// PUT request
void _examplePutRequest() async {
final Map<String, dynamic> data = {"name": "John Doe"};
final Result result = await _httpService.put("/users/1", data: data);
if (result.hasValue) {}
if (result.hasError) {
result.requireError.message.log();
}
}
// DELETE request
void _exampleDeleteRequest() async {
final Result result = await _httpService.delete("/users/1");
if (result.hasValue) {}
if (result.hasError) {
result.requireError.message.log();
}
}
// Single file download
void _exampleDownload() async {
final Result result = await _httpService.download(
url: "https://example.com/file.pdf",
savePath: "example.pdf",
onProgress: (received, total) {
// Progress handling
},
);
if (result.hasValue) {}
if (result.hasError) {
result.requireError.message.log();
}
}
}
Error / Result Management for Result
Future<Result<int, FirebaseException>> getUserId() async {
try {
final userId = await _authService.getUserId();
return Result.value(input: userId);
} on FirebaseException catch (e) {
return Result.error(input: e);
}
}
final result = await getUserId();
if (result.hasError) {
result.requireError.log();
} else {
result.requireValue.log();
}
For application theme;
// app/theme.dart
class ThemeProvider extends BaseTheme {}
// http.dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ThemeProvider>(create: (_) => ThemeProvider()),
],
builder: (context, child) {
final themeProvider = context.watch<ThemeProvider>();
return MaterialApp.router(
title: 'Example',
theme: themeProvider.themeData,
themeMode: themeProvider.themeMode,
routerConfig: AppRouter.router,
debugShowCheckedModeBanner: false,
);
},
);
}
}
Gap usage
Row(
children: [
Gap(10),
Text('Hello'),
Gap(10),
],
)
...
Column(
children: [
Gap(5),
Text('World'),
Gap(5),
],
)
Fluid Dialog usage
OutlinedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => FluidDialog(
rootPage: FluidDialogPage(
alignment: Alignment.bottomCenter,
builder: (context) => const TestDialog(),
),
),
);
},
child: const Text('Open Dialog'),
),
//--- TestDialog Widget
TextButton(
onPressed: () => DialogNavigator.of(context).push(
FluidDialogPage(
// New dialog page to be displayed
builder: (context) => const SecondDialogPage(),
),
),
child: const Text('Go to next page'),
),
Loading Dialog Usage
// Without Extension
ElevatedButton(
onPressed: () async {
LoadingDialog.instance.setDefaultView();
LoadingDialog.instance.show(context: context, text: "Fetching..");
await Future.delayed(const Duration(seconds: 2));
if (context.mounted) {
LoadingDialog.instance
.show(context: context, text: "Almost Done");
}
await Future.delayed(const Duration(seconds: 1));
LoadingDialog.instance.hide();
},
child: const Text("Show Loading UseCase 2"),
),
// With Extension
ElevatedButton(
onPressed: () {
LoadingDialog.instance.setView(const CustomLoadingView());
Future.delayed(const Duration(seconds: 2))
.showWithLoadingDialog(context: context, text: "Loading..");
},
child: const Text("Show Loading UseCase 1"),
),
Figma-style text with stroke effect
TextWithStroke(
text: 'Flutter with Stroke',
style: TextStyle(
fontSize: 120,
color: Color(0xFF7DCFFF),
),
),
RichTextWidget
body: Padding(
padding: const EdgeInsets.all(8.0),
child: RichTextWidget(
styleForAll: Theme.of(context).textTheme.displayMedium,
texts: [
BaseText.plain(
text: 'Welcome to my blog at ',
),
BaseText.link(
text: 'https://example.com/',
onTap: () {
'Tapped'.log();
},
),
],
),
);
),
Useful Extensions
String Extensions
"exAmpLE".capitalize; // 'Example'
"example@gmail.com".isMail; // true
"örNek".toLowerCaseTurkish; // 'örnek'
"örNek".toUpperCaseTurkish; // 'ÖRNEK'
"örNek".onlyLatin; // 'rNek'
"<h1>Example</h1>".removeHtmlTags; // 'Example'
"Example text".countWords; // 2
"exam47p34le".removeNumbers; // 'example'
"exa1m24pl3e".onlyNumbers; // '1243'
"(example/!@#\$%^".removeSpecialChars; // 'example'
"es4e5523nt1is".removeLetters; // '455231'
"sLuG Case".toSlug; // 'sLuG_Case'
"SNAKE CASE".toSnakeCase; // 'snake_case'
"Find max of array".toCamelCase; // 'findMaxOfArray'
"Hello my name is example".toTitleCase; // 'Hello My Name Is Example'
// Reads the file on the Assets
// Returns Uint8List data
await 'images/template.png'.localFileData())
DateTime Extensions
DateTime.now.passingTime(DateTime.now().add(Duration(days: 1))); // '1 day ago'
DateTime.now.passingTime(DateTime.now().add(Duration(days: 30))); // '30 days ago'
DateTime.now.passingTime(short: true, DateTime.now().add(Duration(days: 30))); // '30d'
Number Extensions
12.sbh(); // SizedBox(height: 12)
12.sbw(); // SizedBox(width: 12)
0.2.vw; // Screen Width * 0.2
0.2.vh; // Screen Height * 0.2
Context Extensions
context.colors; // Theme.of(context).colorScheme
context.textTheme; // Theme.of(context).textTheme
context.mediaQuery; // MediaQuery.of(context)
context.height; // MediaQuery.sizeOf(context).height
context.width; // MediaQuery.sizeOf(context).width
context.lowValue; // context.height * 0.01
context.defaultValue; // context.height * 0.02
context.highValue; // context.height * 0.05
context.veryhighValue1x; // context.height * 0.1
context.veryhighValue2x; // context.height * 0.2
context.veryhighValue3x; // context.height * 0.3
context.veryhighValue4x; // context.height * 0.4
context.veryhighValue5x; // context.height * 0.5
context.dynamicWidth(0.2); // context.width * 0.2
context.dynamicHeight(0.2); // context.height * 0.2
context.paddingAllLow; // EdgeInsets.all(context.lowValue)
context.paddingAllDefault; // EdgeInsets.all(context.defaultValue)
context.paddingAllHigh; // EdgeInsets.all(context.highValue)
context.paddingHorizontalLow; // EdgeInsets.symmetric(horizontal: context.lowValue)
context.paddingHorizontalDefault; // EdgeInsets.symmetric(horizontal: context.defaultValue)
context.paddingHorizontalHigh; // EdgeInsets.symmetric(horizontal: context.highValue)
context.paddingVerticalLow; // EdgeInsets.symmetric(vertical: context.lowValue)
context.paddingVerticalDefault; // EdgeInsets.symmetric(vertical: context.defaultValue)
context.paddingVerticalHigh; // EdgeInsets.symmetric(vertical: context.highValue)
... and more
Function Extensions
// Waiting operation in VoidCallback functions
_counterClockwiseRotationController.forward.delayedCall(
const Duration(
seconds: 1,
),
);
// Future Unwrap function (Null Safety)
static Future<File> pickImageFromGallery() => _imagePicker
.pickImage(source: ImageSource.gallery)
.unwrap() // null check
.then((xFile) => xFile.path)
.then((filePath) => File(filePath))
/// Helper extension that returns different widgets based on FutureBuilder states.
///
/// - Calls [onNone] widget in `ConnectionState.none` state.
/// - Calls [onWaiting] widget in `ConnectionState.waiting` or `active` state.
/// - In `ConnectionState.done`, calls [onData] if there’s data,
/// [onError] if there’s an error, or [onDoneWithoutDataOrError] if neither is present.
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: getName(),
builder: (context, snapshot) => snapshot.futureBuilder(
context: context,
onData: (_, name) => Text(name),
// onError: ..,
// onDoneWithoutDataOrError: ..,
// onWaiting: ..,
),
),
);
}
Stream Extensions
// Waiting operation between events
await for (final name in getNames().withTimeoutBetweenEvents(
const Duration(
seconds: 3,
),
)) {
// object.log extension
name.log();
}
// Stream Error Recovery operation
Stream<String> getNames() async* {
yield 'Foo';
yield 'Bar';
throw Exception('Something went wrong');
}
final names = getNames().onErrorRecoverWith(
(error) {
error.log();
return 'Baz';
},
);
await for (final name in names) {
name.log(); // Foo, Bar, Baz
}
// Stopping the Stream itself if an error occurs
Stream<String> getNames() async* {
yield 'Vandad';
await Future.delayed(const Duration(seconds: 1));
yield 'John';
await Future.delayed(const Duration(seconds: 1));
throw 'Enough names for you';
}
Future<void> testIt() async {
await for (final name in getNames().absorbErrors()) {
name.log(); // Vandad, John, then stream closes
}
}
Object Extensions
// Flattening the list
void testIt() {
final flat = [
[[1, 2, 3], 4, 5],
[6, [7, [8, 9]], 10],
11,12
].flatten().log();
// (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
}
// Advanced Where filtering operation on Map object
people.where((key, value) => key.length > 4 && value > 20).log();
// {Peter: 22}
people.whereKey((key) => key.length < 5).log();
// {John: 20, Mary: 21}
people.whereValue((value) => value.isEven).log();
// {John: 20, Peter: 22}