Grails saves datetime as UTC time, but reads it as local server time??? |
The timestamp is read back as local time instead. So if my timezone is +2 UTC and the current local time is 12:30:00, what will be saved to the database is 10:30:00, but when I read it back, it becomes 10:30:00. Does anybody know how to fix this problem so that Grails will read the timestamp back as UTC and convert it accordingly? |
I have the following line in my Grails application (BootStrap.groovy) to set the default timezone to UTC: |
TimeZone.setDefault(TimeZone.getTimeZone("UTC")) |
And above line solved my problem. Now I can set time anything to db and returned back the same date to my domain field |
Tuesday, October 12, 2021
Grails saves datetime as UTC time, but reads it as local server time
Flutter Date Picker - Customization of date picker with color and font size - dive into date picker
How do we give option to the user to enter the date which provides the best experience to them?
Instead of creating our own UI to let the user enter the date, The best solution is to use an already available view or widget like DatePicker in the respective Platform. Shows a dialog containing a Material Design date picker. The initialDate property is used to display a default date when DatePicker dialog is opened. SetState will be called to update the selected date in UI and you are done. |
Minimum code to open a date picker:void openDatePicker(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, firstDate: DateTime(2020), lastDate: DateTime(2025), ); if (picked != null && picked != selectedDate) { setState(() { selectedDate = picked; }); } } This method actually calls showDatePicker function and waits for the date selected by the user. If a user does not select anything then the date return will be null otherwise the selected date. |
Show/display date but maintain a date range to select date?
By setting the firstDate and the lastDate properties you can't select date outside the range. |
Show the text input box instead of the calendar view
void openDatePicker(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, // initial date firstDate: DateTime(2020), // date range limit from lastDate: DateTime(2025), // date range limit upto initialEntryMode: DatePickerEntryMode.input, // show text input mode ); if (picked != null && picked != selectedDate) { setState(() { selectedDate = picked; }); } } Available options are: calendar: will show calendar when load then you can change to input field mode. input: will show input field mode when load then you can change to calendar view calendarOnly: will show calendar view mode and user can't change to input field mode inputOnly: will show input field mode only and user can't change to calendar view |
Show the year list first when open date picker
This option [initialDatePickerMode: DatePickerMode.year] open date picker to select year first, then user can select date later once year selected. void openDatePicker(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, // initial date firstDate: DateTime(2020), // date range limit from lastDate: DateTime(2025), // date range limit upto initialEntryMode: DatePickerEntryMode.calendar, // show text input mode initialDatePickerMode: DatePickerMode.year, // will show year selection first ); if (picked != null && picked != selectedDate) { setState(() { selectedDate = picked; }); } } |
Allow the user to enter a date from a specific day range
void openDatePicker(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, firstDate: DateTime(2020), lastDate: DateTime(2025), selectableDayPredicate: _decideWhichDayToEnable, ); if (picked != null && picked != selectedDate) { setState(() { selectedDate = picked; }); } } bool _decideWhichDayToEnable(DateTime day) { if ((day.isAfter(DateTime.now().subtract(Duration(days: 1))) && day.isBefore(DateTime.now().add(Duration(days: 10))))) { return true; } return false; } |
Change header title and button text
void openDatePicker(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, firstDate: DateTime(2020), lastDate: DateTime(2025), helpText: 'Select Some Date', cancelText: 'No Thanks', confirmText: 'Select This Date', ); if (picked != null && picked != selectedDate) { setState(() { selectedDate = picked; }); } } |
Change date picker display theme like font color, background color, font size, button color and size
void openDatePicker(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate, firstDate: DateTime(2020), lastDate: DateTime(2025), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: Colors.green, primaryVariant: Colors.black, secondaryVariant: Colors.black, onSecondary: Colors.black, onPrimary: Colors.white, surface: Colors.black, onSurface: Colors.black, secondary: Colors.black ), textTheme: const TextTheme( headline4: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold),//3 bodyText1: TextStyle(fontSize: 25.0),//year selection subtitle2: TextStyle(fontSize: 22.0), //2 caption: TextStyle(fontSize: 24.0),//day selection overline: TextStyle(fontSize: 22.0), //1 ), dialogBackgroundColor: Colors.lightBlueAccent, textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( primary: Colors.red, // button text color textStyle: TextStyle( fontSize: 22, // button text size ) ), ), ), child: child!, ); }, ); if (picked != null && picked != selectedDate) { setState(() { selectedDate = picked; }); } } |
Saturday, October 9, 2021
Flutter Popup Menu Button Example Tutorial
In flutter, popup menu button widget simply a popup / overflow menu in android and ios. It is similar to flutter dropdownButton but has additional features. We will learn how to attach a popup menu button widget in flutter and its properties in details. |
In flutter, popup menu button widget displays an overflow menu when pressed. When we select an item the onSelected callback will be fired and the menu is dismissed as well. The value of the menu item selected by the end user will be available with onSelected callback. We can use the value to trigger actions as of our requirements. |
The itemBuilder property is required which means without using it will throw an error. We have to use the Stateful widget as popup menu button will have a change in the state based on the user selection. |
We should use either child or icon property but not both as it will throw an error. |
The icon property is used to change the icon of the popup menu. By default, the popup menu displays an overflow menu(three dots) icon even if we don’t use the icon property. |
We will use the itemBuilder property to add items to Popup Menu Button. It accepts a list of PopupMenuItems. |
We will use the elevation property to apply elevation to the popup menu. Elevation makes the popup menu look as it is lifted upward from the background. |
Full example given below: |
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: ApplicationAppBar(), body: const MyHomePageImpl() ); } } class MyHomePageImpl extends StatefulWidget { const MyHomePageImpl({Key? key}) : super(key: key); @override _MyHomePageImpl createState() => _MyHomePageImpl(); } class _MyHomePageImpl extends State<MyHomePageImpl> { @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(20), child: Column( children: [ const SizedBox(height: 5), Row( children: [ Expanded( child: Text("Hello") ), Container( child: PopupMenuButton( icon: Icon(Icons.more_horiz), elevation: 40, shape: const OutlineInputBorder( borderSide: BorderSide( color: Colors.green, width: 2 ) ), enabled: true, onSelected: (value) { print("Menu [$value] selected"); }, onCanceled: () { print("None selected"); }, itemBuilder: (context) => [ PopupMenuItem( child: Text("Menu 1"), value: "menu_1", ), PopupMenuItem( child: Text("Menu 2"), value: "menu_2", ), PopupMenuItem( child: Text("Menu 3"), value: "menu_3", enabled: false, ), ] ), //padding: const EdgeInsets.all(6.0), decoration: BoxDecoration( border: Border.all( color: Colors.lightGreen, ), borderRadius: BorderRadius.all(Radius.circular(23)), ), ) ], ), ], ), ); } } class ApplicationAppBar extends AppBar { ApplicationAppBar({Key? key}) : super(key: key, title: const Text("Popup Menu Button"), actions: [ IconButton(icon: const Icon(Icons.add), onPressed: () {}), ], ); } |
Ouptu screenshot as below: |
Monday, October 4, 2021
Grails 4 - how to attach domain entity manually to session and marked as readonly mode in hibernate session
Basically when an entity loaded from database via hibernate transaction manager then automatically attached to hibernate session. If we use Domain.read() then domain entity attached with hibernate session but marked as readonly mode. In such case domain don't get updated on any change, because they marked as readonly mode. But what will happen if you want to attach entity in hibernate session manually and mark them as readonly mode. |
To understand check below example: Account account = new Account() account.id = 10 // we already know that there is an entity with id 10 // and we don't want to fetch data from database Order order = new Order() order.account = account order.save() |
Above example will execute but when the statement [order.account = account] executed, a fetch operation will take place like: select account.id,account..... from account where account.id=10 because account entity is not attached with current hibernate session. To overcome this problem we will manually attach account entity to hibernate session and mark them as readonly state using: |
import org.hibernate.internal.SessionImpl Account.withSession { SessionImpl session -> account.attach() session.setReadOnly(account, true) // readonly entity will not persist in database } |
Now when saving order, account entity already attached with hibernate session and no additional query will be executed. This mechanism will help you when you know that an entity already exising and you don't want to fetch from db. |
Here is another fantastic solution I hope you will like this idea.
As of latest hibernate version, all domain entities must be attached to session (previous hibernate version don't recommend this) So check below case: Account account = new Account() account.id = 10 // we already know that there is an entity with id 10 // and we don't want to fetch data from database Order order = new Order() order.account = account When assign account to order, system expect account must be attached with session as account and order both are domain entities. And because of account is not attached with current session, an select query will took place here to fetch data from database. |
To avoid this we can do some simple trick so that without reading one entity from database we can continue our operation.
At first we need to add a Map field to each domain class to hold some value like: class Account implements MyDomainInterface { Object TEMP = [:] Object ATTACHED_OBJECT_ENTITIES = [:] Long id void setAsCarbonCopy() { this.TEMP.existing = true } Boolean isCarbonCopy(Object o = null) { return this.TEMP.containsKey("existing") } } You need to do same for order domain class. Then we need to extend metaclass like: import grails.core.GrailsApplication import org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack GrailsApplication grailsApplication grailsApplication.domainClasses.each { domainClass -> domainClass.metaClass.setProperty = { String propertyName, propertyValue -> MetaProperty metaProperty = domainClass.metaClass.getMetaProperty(propertyName); if (metaProperty) { if (propertyValue instanceof MyDomainInterface && propertyValue.isCarbonCopy()) { //we will set an proxy instance rather then real entity //proxy entity by default attached with session //but no addition query run for data collection until call explicitly Object no = propertyValue.class.proxy(propertyValue.id) delegate.ATTACHED_OBJECT_ENTITIES[propertyName] = propertyValue metaProperty.setProperty(delegate, no) } else { metaProperty.setProperty(delegate, propertyValue) } } else { throw new MissingPropertyExceptionNoStack(propertyName, domainClass.metaClass.theClass) } } domainClass.metaClass.getProperty = { String propertyName -> if (propertyName != "ATTACHED_OBJECT_ENTITIES" && delegate.ATTACHED_OBJECT_ENTITIES.containsKey(propertyName)) { //we will return original domain entity //rather than proxy entity return delegate.ATTACHED_OBJECT_ENTITIES[propertyName] } MetaProperty metaProperty = domainClass.metaClass.getMetaProperty(propertyName) return metaProperty.getProperty(delegate) } } |
Then do the logic as follows:
Account account = new Account() account.id = 10 account.setAsCarbonCopy() Order order = new Order() order.account = account // now no additional query will run You can get order.account which will return above pojo entity as we modified our meta method. So any change on account and/or order.account will not automatically persist in our database as account is nothing but a pojo entity. And is not attach with session, we attached Account.proxy() with hibernate session, not pojo entity. |
Sunday, October 3, 2021
Flutter - design application with scrollbar enabled body with fixed header and footer | Header, Footer And Scrollable Body | How to create a scroll view with fixed footer with Flutter
A Flutter Widget that is scrollable with a sticky header to the header and footer to the bottom of screen of the end of the scroll body. You may simiply test with the example to see whether it is fit for your case. itemBuilder to build child widgets of scroll body; itemCount is the nunber of child widgets in scroll body.
Flutter is a mobile App SDK by Google which helps in creating Flutter: Material Design Using Scaffold AppBar Body Bottom Navigation Floating Action & Persistent Footer |
ListView is the most commonly used scrolling widget. It displays its children one after another in the scroll direction. In the cross axis, the children are required to fill the ListView.
The default constructor takes an explicit List The ListView.builder constructor takes an IndexedWidgetBuilder, which builds the children on demand. This constructor is appropriate for list views with a large (or infinite) number of children because the builder is called only for those children that are actually visible. |
The ListView.separated constructor takes two IndexedWidgetBuilders: itemBuilder builds child items on demand, and separatorBuilder similarly builds separator children which appear in between the child items. This constructor is appropriate for list views with a fixed number of children. |
ListView documentation |
Full example as below: |
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: ApplicationAppBar(), body: MyHomePageImpl() ); } } class MyHomePageImpl extends StatefulWidget { @override _MyHomePageImpl createState() => _MyHomePageImpl(); } class _MyHomePageImpl extends State<MyHomePageImpl> { final Future<int> loadDataAsync = Future<int>.delayed( Duration(seconds: 1), () async => processDataAsync(), ); static Future<int> processDataAsync() async { return 20; } @override Widget build(BuildContext context) { return Column( children: [ SizedBox(height: 5), Container( width: double.infinity, padding: EdgeInsets.all(20), margin: EdgeInsets.fromLTRB(10, 10, 7, 10), child: Text("Fixed Header"), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Colors.red, ), borderRadius: BorderRadius.all(Radius.circular(10)) ), ), Expanded( child: FutureBuilder( builder: (context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( Icons.error_outline, color: Colors.red, size: 60, ), Padding( padding: const EdgeInsets.only(top: 16), child: Text('Error: ${snapshot.error}'), ) ], ), ); } if (!snapshot.hasData) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: 20), SizedBox( child: CircularProgressIndicator(), width: 60, height: 60, ), const Padding( padding: EdgeInsets.only(top: 16), child: Text('Awaiting result...'), ) ], ), ); } return Column( children: [ Expanded( child: Container( padding: EdgeInsets.fromLTRB(0, 10, 0, 10), margin: EdgeInsets.fromLTRB(10, 0, 10, 0), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Colors.red, ), borderRadius: BorderRadius.all(Radius.circular(10)) ), child: ListView.builder( physics: ScrollPhysics(), shrinkWrap: true, itemCount: snapshot.data, itemBuilder: (BuildContext context, int index) { return Padding( padding: EdgeInsets.fromLTRB(10, 0, 10, 10), child: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Colors.lightGreen, ), borderRadius: BorderRadius.all(Radius.circular(10)) ), child: Center( child: Text( 'Child $index', style: TextStyle(color: Colors.black, fontSize: 22), ), ), ), ); }, ), ) ) ], ); }, future: loadDataAsync, ), ), Container( width: double.infinity, padding: EdgeInsets.all(20), margin: EdgeInsets.fromLTRB(10, 10, 10, 0), child: Text("Fixed Footer"), decoration: BoxDecoration( color: Colors.white, border: Border.all( color: Colors.red, ), borderRadius: BorderRadius.all(Radius.circular(10)) ), ), SizedBox(height: 5), ], ); } } class ApplicationAppBar extends AppBar { ApplicationAppBar() : super( title: Text("Fixed Header & Footer"), actions: [ IconButton(icon: Icon(Icons.add), onPressed: () {}), ], ); } |
Sample screenshot: |
Wednesday, September 29, 2021
GRAILS 4 - how to disable deepvalidate in grails globally | Add ability to control cascading validation independently
How can we disable deepvalidate on global level in grails 4? as in our case on saving one domain object its trying to save all internal domain objects leading to different errors like unique constraint and all.
If GORM entity references some other entities, then during its constraints evaluation (validation) the constraints of the referenced entity could be evaluated also, if needed. There is a special parameter cascadeValidate in the entity mappings section, which manage the way of this cascaded validation happens. You can do this in three ways |
1. Define cascadeValidate as mapping per domain where needed: class Author { Publisher publisher static mapping = { publisher(cascadeValidate: "none") } } class Publisher { String name static constraints = { name blank: false } } |
The following table presents all options, which can be used: none: Will not do any cascade validation at all for the association. default: The DEFAULT option. GORM performs cascade validation in some cases. dirty: Only cascade validation if the referenced object is dirty via the DirtyCheckable trait. If the object doesn’t implement DirtyCheckable, this will fall back to default. owned: Only cascade validation if the entity owns the referenced object. |
2. It is possible to set the global option for the cascadeValidate: Globally disable cascadeValidate in Grails 3 or 4 using: grails { gorm { failOnError = true 'default' { mapping = { cache true version false autoTimestamp false id generator:'assigned' '*'(cascadeValidate: 'none') // this one is the option to disable deep validate } } } } |
3. Alternatevely you can disable when call save() or merge() using: new Account().save(validate: true, deepValidate: false) |
Grails gorm reference link: Reference https://gorm.grails.org/latest/hibernate/manual/#_cascade_constraints_validation |
Tuesday, September 28, 2021
Flutter – How to limit keyboard to allow digits only - How to Make TextField Number only in Flutter - Digits Only Input Keyboard in Flutter – Only Numbers on Text Field - How to Create Number Inputfield in Flutter
This post is just my quick note on how to limit the soft keyboard to allow only digits.
TextField is one of the most commonly used widgets in Flutter. It helps users to enter their text input. In this tutorial, let’s learn how to make the textfield number only by changing the keyboard type in Flutter and apply regex to restrict other than valid numbers. Kyebord type input only show a keyboard with number inputs, but you can add like 4....4 which is not a number anyway, so let apply regex too to avoid such type of situation. |
You can specify the number as keyboard type for the TextField Widget using:
keyboardType: TextInputType.number Through this option, you can strictly restrict another char without a number. inputFormatters: [FilteringTextInputFormatter.digitsOnly], keyboardType: TextInputType.number, |
Lets apply regex to TextField to avoid unexpected input to TextField: I'm going to full example, first, created a file named input_validation_utils.dart with following contents: |
import 'package:flutter/services.dart'; abstract class StringValidator { bool isValid(String value); } class DecimalNumberEditValidator extends RegexValidator { DecimalNumberEditValidator() : super(regexSource: "^\$|^(0|([1-9][0-9]{0,7}))(\\.[0-9]{0,2})?\$"); } class RegexValidator implements StringValidator { RegexValidator({required this.regexSource}); final String regexSource; /// value: the input string /// returns: true if the input string is a full match for regexSource bool isValid(String value) { try { final regex = RegExp(regexSource); final matches = regex.allMatches(value); for (Match match in matches) { if (match.start == 0 && match.end == value.length) { return true; } } return false; } catch (e) { // Invalid regex assert(false, e.toString()); return true; } } } class ValidatorInputFormatter implements TextInputFormatter { ValidatorInputFormatter({required this.editingValidator}); final StringValidator editingValidator; TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) { final oldValueValid = editingValidator.isValid(oldValue.text); final newValueValid = editingValidator.isValid(newValue.text); if (oldValueValid && !newValueValid) { return oldValue; } return newValue; } } |
Then we need to apply above validator to a TextField to restrict input other than numbers, now main.dart is looks like: |
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:p1/input_validation_utils.dart'; void main() => runApp(new MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { final String title = "Regex Input Validation"; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: ListView( children: <Widget>[ SizedBox(height: 30,), TextField( decoration: InputDecoration.collapsed(hintText: '\$0.00'), style: TextStyle(fontSize: 32.0, color: Colors.black87), textAlign: TextAlign.center, keyboardType: TextInputType.number, autofocus: true, //disabled auto correct if regex failed autocorrect: false, textInputAction: TextInputAction.done, inputFormatters: [ //applying our custom formatter to TextField //so that input other than number can be input ValidatorInputFormatter( editingValidator: DecimalNumberEditValidator(), ) ], onChanged: (value) { print("Input=${value}"); }, ), SizedBox(height: 30,), OutlinedButton( onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => MyHomePage()), ); }, child: Text("Forward") ) ], ), ); } } |
Sample screen shot as below: |
Subscribe to:
Posts (Atom)