I have never traveled out of my country but I’ve been told that traveling is cool.

And today we will make an app exactly about that!

As usual we have two app screens, first one is the Login/Register screen, and the other one is the main app screen.

Get the source code: https://github.com/cybdom/flutter_travel_app

And let’s get started:

Sign Up / Sign In:

Before talking about the code I have to brag about the logo I made, I am so proud of it! (Just kidding, it took me about 5 minutes to do in photoshop).

So how do we create a background image in flutter? Just use a Stack!

Positioned( bottom: 0, top: 0, right: 0, left: 0, child: Image.asset( "assets/bg.jpg", fit: BoxFit.fitHeight, ), ),

Pretty simple isn’t it? I used the Positioned widget to fill the Viewport space, I could’ve used Positioned.Fill but I wanted to write the “0” by my self.

Above the background we have a Container, we set its width to double.infinity in order to cover the width of the viewport,

The container has a child of a column as you can see here:

Column( children: <Widget>[ Spacer(), Image.asset( "assets/icon.png", width: 150, ), Spacer(), Text( "TRAVaL", style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 31, ), ), Text( "Prepare your own trip with us", style: TextStyle( color: Colors.white, fontSize: 21, ), ), Spacer(), MyButton( buttonColor: Color(0xff48808d), border: null, buttonText: "Sign Up", ), SizedBox( height: 11, ), MyButton( buttonColor: Colors.transparent, border: Border.all( color: Colors.white, ), buttonText: "Sign In", ), Spacer(), ], ), ) ], ),

Nothing special, you may ask about the Spacer widget, I only use it to keep things well aligned, here I wanted to put the “Marketing” text in the middle of the screen.

You can also see we have a special Widget called MyButton, I made it my self, and here is the code:

class MyButton extends StatelessWidget { final Color buttonColor; final Border border; final String buttonText; const MyButton( {Key key, @required this.buttonColor, this.border, this.buttonText}) : super(key: key); @override Widget build(BuildContext context) { return InkWell( onTap: () { Navigator.pushNamed(context, '/home'); }, child: Container( width: MediaQuery.of(context).size.width / 2.3, decoration: BoxDecoration( color: buttonColor, borderRadius: BorderRadius.circular(15), border: border, ), padding: EdgeInsets.symmetric(vertical: 9.0), child: Center( child: Text( buttonText, style: TextStyle(color: Colors.white, fontSize: 21), ), ), ), ); } }

It takes three variables:

ButtonColor

Border

ButtonText

ButtonColor is the background color of the button.

Border is the white border you can see on the second button, the first button doesn’t have any.

ButtonText is simply the button text, I guess it’s pretty obvious.

I set the button width to 2.3 of the Viewport size I just feel that it looks good this way.

I guess we can now get to part two.

Home screen:

The UI looks really simple and clean however there are some coding challenges when you try to replicate it using flutter.

After creating the “AppBar” which is of course just a Row, as I don’t like to use the standard AppBar widget. I wrote the simple logic for this selector:

The user has to either select a Flight or a Hotel or both, he can also unselect both but the Search button if pressed with show a SnackBar error.

The code I used isn’t production-ready as I just used Global variables to achieve this result but you can work on it if you want. For the meanwhile, I just wanted something that works.

This is the Widget I created:

class MySpecialSelector extends StatefulWidget { @override _MySpecialSelectorState createState() => _MySpecialSelectorState(); } class _MySpecialSelectorState extends State<MySpecialSelector> { @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ GestureDetector( onTap: () { setState(() { flightSelected = !flightSelected; print(flightSelected); }); }, child: Text( "Flight", style: TextStyle( color: flightSelected ? Color(0xff48808d) : Colors.grey[600], fontSize: 21, fontWeight: FontWeight.bold, ), ), ), Container( padding: EdgeInsets.all(5.0), decoration: BoxDecoration( border: Border.all(color: Colors.grey[600]), shape: BoxShape.circle), child: Icon( Icons.add, color: Colors.grey[600], ), ), GestureDetector( onTap: () { setState(() { hotelSelected = !hotelSelected; }); }, child: Text( "Hotel", style: TextStyle( color: hotelSelected ? Color(0xff48808d) : Colors.grey[600], fontWeight: FontWeight.bold, fontSize: 21, ), ), ), ], ); } }

The only thing that changes is the Color of the text so that when it’s selected the text is Blueish, otherwise it’s grey.

Straight after that, I went to build the Search button, you may think it’s straight forward but it’s not.

Builder( builder: (context) { return Material( color: Colors.transparent, child: InkWell( onTap: () { if (!flightSelected && !hotelSelected) { Scaffold.of(context).showSnackBar( SnackBar( content: Text( 'Please select at least Hotel or Flight', textAlign: TextAlign.center, ), ), ); } }, child: Container( padding: EdgeInsets.symmetric(vertical: 19.0), width: MediaQuery.of(context).size.width / 2.5, decoration: BoxDecoration( color: Color(0xffe6eef0), borderRadius: BorderRadius.circular(35), ), child: Center( child: Text( "Search", style: TextStyle( fontWeight: FontWeight.bold, color: Colors.black, ), ), ), ), ), ); }, ),

I had to use a Builder because otherwise, I couldn’t access the Context of the Scaffold, thus I wouldn’t be able to show the Snackbar. Other than that we check if either the Hotel or Flight option is selected otherwise we show a Snackbar Error.

Now let’s go to my Lovely Special Card:

class MySpecialCard extends StatelessWidget { @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.symmetric(vertical: 25.0), padding: EdgeInsets.all(15.0), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(21.0), boxShadow: [ BoxShadow( blurRadius: 5.0, color: Colors.grey[300], offset: Offset(0, 1), ), ], ), child: Column( children: <Widget>[ TripSelector(), Spacer(), SizedBox( height: 11, ), AppointmentContainer(text: "Where from ?"), Row( children: <Widget>[ Spacer(), Container( decoration: BoxDecoration( color: Colors.grey[200], shape: BoxShape.circle), child: IconButton( icon: Icon(Icons.swap_vert, color: Colors.grey[600]), onPressed: () {}, ), ) ], ), SizedBox( height: 9.0, ), AppointmentContainer(text: "Where to ?"), SizedBox( height: 9.0, ), Row( children: <Widget>[ Spacer(), Icon( Icons.person, color: Colors.grey[600], ), SizedBox( width: 11, ), Expanded( child: TextField( decoration: InputDecoration( filled: true, fillColor: Color(0xfff6f6f6), border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), borderSide: BorderSide.none), hintText: "1 Person"), ), ), Spacer(), ], ), Spacer(), ], ), ); } }

Where do we get started? let’s see what is repeating and then get to the unique stuff (If any).

So AppointmentContainer is this:

It consists of a Column, of a TextField with inputDecoration as follow:

TextField( decoration: InputDecoration( filled: true, fillColor: Color(0xfff6f6f6), border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), borderSide: BorderSide.none), hintText: text), ),

And a second child of a Row containing an Icon, and another TextField for the Departure/Arrival date.

Next, we can talk about this:

Row( children: <Widget>[ Spacer(), Icon( Icons.person, color: Colors.grey[600], ), SizedBox( width: 11, ), Expanded( child: TextField( decoration: InputDecoration( filled: true, fillColor: Color(0xfff6f6f6), border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), borderSide: BorderSide.none), hintText: "1 Person"), ), ), Spacer(), ], ),

It’s pretty much the same as the other Textfields but in a smaller format because I wanted it to be in the Center and also to have an icon to it’s left and this is how I achieved this result.

Now the Header of the card, it’s kind of the same as the Hotel/flight selector but we don’t need to have “Global” variables this time.

And instead of changing the text color, we underline it.

GestureDetector( onTap: () { setState(() { selectedId = 0; }); }, child: Text( "Round", style: TextStyle( decoration: selectedId == 0 ? TextDecoration.underline : TextDecoration.none), ), ),

This time we use selectedId to know which item is selected. If selected then we add a decoration of type TextDecoration.underline otherwise, we set it to TextDecoration.underline.

Conclusion:

This is it for today’s tutorial! I know that I didn’t go through all the details, but the concept of this blog is to push you to try by yourself.

IIn my opinion the best to learn is to try, make mistakes, and improve yourself.

Even though I am not the best coder out there, neither am I the best teacher, but I try to improve myself every day.

Do you agree with me? do you like my style? then please share this article with everyone else, they may like it too!