Today we are taking a look at a simplistic real estate app, building this UI involves: Google Maps Package, Stacks, ClipRRect, and much more!

As always we start by getting the source code from Github: https://github.com/cybdom/Realestate

Home Screen:

We start here with a Stack just inside our Scaffold this will allow us to have our map in the background along with all of those control and filter Container on the bottom.

Now, the first child of the Stack is our Map we position it in a way to leave one-fifth of the screen size for our bottom container.

Positioned( top: 0, left: 0, right: 0, bottom: MediaQuery.of(context).size.height * .25, child: MyMap(), // child: Container(color: Colors.red,), ),

The second step here is making sure our controls are right-aligned on top of our map, for this purpose we can use different approaches but in my case, I used a Positioned.fill widget inside of which I put a Column. This column has two Flexibles as children, the first has a flex of 5 and the second a Flex of 2. What this does is make sure our bottom Container fits exactly below our map while having that Border Radius that allows us to see the map (that was very important to me).

Another important thing to note is that the Google branding is still showing even though our map expands a bit further down. While using google maps it is important to keep the logo visible in order to avoid legal issues and the way we fix that is by setting a padding value on our map widget.

Going back to the Controls, we’ve got three IconButtons inside a Column each of them is wrapped inside a Container in order to give the buttons a BoxShadow.

Container( decoration: BoxDecoration( boxShadow: [ BoxShadow( blurRadius: 3.0, color: Colors.black45, ) ], color: Colors.white, borderRadius: BorderRadius.circular(9.0), ), child: IconButton( icon: Icon(Icons.bookmark_border, color: purple), onPressed: () {}, ), ),

Bottom Sheet ?? Filter Container:

The nice thing about flutter is that you can either use out of the box widgets or create your own using a combination of what is available. For example, we could use a Bottom Sheet in this situation but I preferred to create my own.

Container( padding: const EdgeInsets.all(21.0), decoration: BoxDecoration( boxShadow: [ BoxShadow( blurRadius: 13, offset: Offset(0, -1), color: Colors.black12, ), ], color: Colors.white, borderRadius: BorderRadius.only( topLeft: Radius.circular(25.0), topRight: Radius.circular(25.0), ), ), child: Column( children: [ Row( children: [ Icon(Icons.tune, color: purple), SizedBox(width: 15.0), Text( "Filters", style: Theme.of(context) .textTheme .headline .apply(color: purple), ) ], ), SizedBox(height: 9.0), Container( height: 35, child: ListView( scrollDirection: Axis.horizontal, children: List.generate( filters.length, (f) { return Container( padding: const EdgeInsets.all(7.5), margin: const EdgeInsets.symmetric( horizontal: 9.0), alignment: Alignment.center, decoration: BoxDecoration( border: Border.all( color: Colors.grey.withOpacity(.43), ), ), child: Text( "${filters[f]}", style: Theme.of(context) .textTheme .button .apply(color: purple), ), ); }, ), ), ), Spacer(), Divider( color: purple, thickness: 1, height: 25, ), Spacer(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon(Icons.search, color: purple), onPressed: () => Navigator.pushNamed(context, 'details'), ), IconButton( icon: Icon(Icons.favorite_border, color: purple), onPressed: () {}, ), IconButton( icon: Icon(Icons.bookmark_border, color: purple), onPressed: () {}, ), IconButton( icon: Icon(Icons.person_outline, color: purple), onPressed: () {}, ), ], ), ], ), ),

We begin by giving our Container a white background color, 25 units border-radius on the top corners, as well as a boxShadow.

The container takes a Column as a child which in turn takes 7 children, 4 of which are either SizedBoxes for a limited margin, a divider for a horizontal separation line or Spacer() that allows us to create an adjustable empty space.

Along with that, we have a Row that represents the title of Filter container, a horizontal list with all the applied filters, and finally, another Row that plays the role of a Bottom Navigation Bar.

Details Screen:

Here things get a bit easier, our AppBar is pretty straight forward as you can see:

appBar: AppBar( elevation: 0, iconTheme: IconThemeData(color: purple), backgroundColor: Colors.white, actions: [ IconButton(icon: Icon(Icons.bookmark_border), onPressed: () {}) ], leading: IconButton(icon: Icon(Icons.chevron_left), onPressed: () {}), ),

Right below we’ve got our TextField, which has a label and some text styling as you can see:

TextField( decoration: InputDecoration( labelText: "CITY", labelStyle: Theme.of(context) .textTheme .title .apply(color: Colors.grey), ), maxLines: 1, style: Theme.of(context).textTheme.display1.apply(color: purple), textCapitalization: TextCapitalization.words, ),

We will get back to the List widgets in a minute, but for now, let’s take a look at the bottom container which looks like a bottom navigation bar.

Container( padding: EdgeInsets.symmetric(vertical: 5.0), height: 61, child: Row( children: [ IconButton( icon: Icon(Icons.tune), color: purple, onPressed: () {}, ), SizedBox(width: 15.0), Expanded( child: ListView( scrollDirection: Axis.horizontal, children: List.generate( filters.length, (f) { return Container( padding: const EdgeInsets.all(7.5), margin: const EdgeInsets.symmetric( horizontal: 9.0, vertical: 5.0), alignment: Alignment.center, decoration: BoxDecoration( border: Border.all( color: Colors.grey.withOpacity(.43), ), ), child: Text( "${filters[f]}", style: Theme.of(context) .textTheme .button .apply(color: purple), ), ); }, ), ), ), ], ), )

The difference with the first one that we can see in our home screen is that here instead of having just an Icon we have an IconButton that actually allows us to choose filters for our search. Other than that it’s just the same.

Now back to our list of results:

Expanded( child: ListView.builder( itemBuilder: (ctx, i) { return Container( alignment: Alignment.center, margin: const EdgeInsets.symmetric(vertical: 15.0), height: 151, child: Row( children: [ IconButton( icon: Icon( Icons.favorite_border, color: purple, ), onPressed: () {}, ), Expanded( child: Column( children: [ Expanded( child: Row( children: [ Flexible( flex: 2, child: ClipRRect( borderRadius: BorderRadius.circular(15.0), child: Image.network( "https://cdn.pixabay.com/photo/2017/06/13/22/42/kitchen-2400367_960_720.jpg"), ), ), SizedBox(width: 11.0), Flexible( flex: 3, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: EdgeInsets.all(5.0), decoration: BoxDecoration( color: Colors.blue), child: Text( "For Rent", style: TextStyle( color: Colors.white), ), ), SizedBox( height: 3.0, ), Text("\$1,031 / mo", style: TextStyle( color: purple, fontWeight: FontWeight.bold, fontSize: 17)), SizedBox( height: 3.0, ), Text("131 Maub St, Chicago, IL", style: TextStyle( color: purple, fontWeight: FontWeight.bold)), ], ), ), ], ), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "3 bd", style: Theme.of(context) .textTheme .body2 .apply(color: purple), ), Text( "/", style: Theme.of(context) .textTheme .body2 .apply(color: purple), ), Text( '3 ba', style: Theme.of(context) .textTheme .body2 .apply(color: purple), ), Text( "/", style: Theme.of(context) .textTheme .body2 .apply(color: purple), ), Text( "3,291 sq", style: Theme.of(context) .textTheme .body2 .apply(color: purple), ), ], ) ], ), ) ], ), ); }, ), ),

The list takes the remaining screen space as it is wrapped inside an Expanded widget.

Our list results are containers that are there just to give some vertical margin, inside we’ve got a mix of columns and rows that allow this arrangement of the UI.

An important tip, when you want to give an image rounded corners just wrap it inside ClipRRect and give it a borderRadius, put that inside a container give it some box-shadow and you get a slick result 😉

Did I talk about the map?

I was actually supposed to make a full tutorial just about maps but I didn’t, so let me give you a preview of how we can use GoogleMaps package in flutter.

First things first our myMap is a Stateful widget as we have to initialize GoogleMapController.

The most basic things that a map takes is the type, the initial position, as well as an onMapCreated callback.

In this real estate app, we get to show available offers on the map and for this purpose we use Markers.

One sad thing about the GoogleMaps flutter package is that Markers aren’t widgets :/

For now, let’s use it as it is:

class _MyMapState extends State { Completer _controller = Completer(); Set _markers = { Marker( markerId: MarkerId("house_1"), position: LatLng(47.599399, -122.324909), ), Marker( markerId: MarkerId("house_2"), position: LatLng(47.601684, -122.328375), ), Marker( markerId: MarkerId("house_3"), position: LatLng(47.600364, -122.327718), ), }; @override Widget build(BuildContext context) { return GoogleMap( padding: EdgeInsets.all(25), mapType: MapType.normal, initialCameraPosition: CameraPosition(target: LatLng(47.600364, -122.327718), zoom: 14), markers: _markers, onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ); }

The End.

That’s it for today’s tutorial, we’ve built a simplistic yet intuitive real estate app that you can use as a base on your next project.

We also learned about Stacks, Positioned and we touched a bit on GoogleMaps package.

If you liked this article please share it, and don’t forget to follow me on Twitter for more awesome flutter tutorials.