Open In App

Flutter – Build Hotel Search App

Last Updated : 14 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

The world of mobile applications has seen a tremendous surge in the travel and tourism sector. In this tutorial, we will explore how to develop a hotel search app using Flutter, a powerful cross-platform framework. Flutter provides a rich set of tools and widgets for crafting engaging and performant mobile applications. We will build a simple yet functional hotel search app that allows users to input their destination, check-in and check-out dates, and the number of adults. The app will then fetch and display a list of hotels matching the criteria using the Airbnb RapidAPI. A sample video is given below to get an idea about what we are going to do in this article.

Step-by-Step Implementation

Step 1: Create a project

Start by creating a new Flutter project using the following command in the terminal or command prompt:

flutter create hotels_search_app

Step 2: Add packages

In pubspec.yaml file, add all packages shown in the below image under the dependencies section.

packages

Step 3: Import Packages

Now to use these packages, import it in main.dart file.

Dart




import 'dart:convert';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';


Step 4: MyApp Widget

In the beiginning of main.dart file, we begin with setting up a basic Flutter structure in the main() function, where the app runs as a MyApp widget, utilizing a MaterialApp.

Dart




void main() {
  runApp(const MyApp());
}
  
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gfg Dreambnb',
     theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      debugShowCheckedModeBanner: false,
      home: HotelSearchPage(),
    );
  }
}


Step 5: Create HotelSearchPage Widget

This widget consists of text input fields for the destination, check-in and check-out dates, and the number of adults. These fields are styled with green borders and labels using TextField and InputDecoration

Dart




class HotelSearchPage extends StatefulWidget {
  @override
  State<HotelSearchPage> createState() => _HotelSearchPageState();
}
  
class _HotelSearchPageState extends State  <HotelSearchPage> {
  TextEditingController placeInput = TextEditingController();
  TextEditingController checkinDate = TextEditingController();
  TextEditingController checkoutDate = TextEditingController();
  TextEditingController adults = TextEditingController();
  
  var hotelsList = [];
  Map<String, dynamic>? parsedResponse;
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text('Gfg Dreambnb'),
        centerTitle: true,
      ),
      body:SingleChildScrollView(
         
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            SizedBox(height: 10),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: placeInput,
                    decoration: InputDecoration(
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20)),
                      labelText: "Destination"),
                  ),
                ),
                SizedBox(width: 10),
                Expanded(child: TextField(
                    controller: checkinDate, // editing controller of this TextField
                    decoration: InputDecoration( 
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20)),
                       
                       labelText: "Checkin Date" // label text of field
                    ),
                    readOnly: true// set it true, so that user will not able to edit text
                    onTap: () async {
                      DateTime? pickedDate = await showDatePicker(
                          context: context, initialDate: DateTime.now(),
                          firstDate: DateTime(2000), // DateTime.now() - not to allow to choose before today.
                          lastDate: DateTime(2101)
                      );
                        
                      if(pickedDate != null ){
                          String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate); 
                           
                          setState(() {
                             checkinDate.text = formattedDate; // set output date to TextField value. 
                          });
                      }else{
                          print("Date is not selected");
                      }
                    },
                 )),
                SizedBox(width: 10),
                Expanded(child: TextField(
                    controller: checkoutDate, // editing controller of this TextField
                    decoration: InputDecoration( 
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20)),
                        // icon of text field
                       labelText: "Checkout Date" // label text of field
                    ),
                    readOnly: true// set it true, so that user will not able to edit text
                    onTap: () async {
                      DateTime? pickedDate = await showDatePicker(
                          context: context, initialDate: DateTime.now(),
                          firstDate: DateTime(2000), // DateTime.now() - not to allow to choose before today.
                          lastDate: DateTime(2101)
                      );
                        
                      if(pickedDate != null ){
                          String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate); 
                           
                          setState(() {
                             checkoutDate.text = formattedDate; // set output date to TextField value. 
                          });
                      }else{
                          print("Date is not selected");
                      }
                    },
                 )),
                 SizedBox(width: 10),
                 Expanded(
                  child: TextField(
                    controller: adults,
                    decoration: InputDecoration(
                      labelText: "Enter adults",
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20))),
                  ),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _getDetails,
                  child: Text("Search"),
                ),
              ],
            ),
          
          ],
        ),
        )
    );
  }
}


Step 6: Create _getDetails function

The _getDetails() function fetches hotel data by making an HTTP GET request to the Airbnb RapidAPI. It constructs the necessary query parameters based on user input (location, dates, adults) and sends the request with appropriate headers.

Dart




_getDetails() async {
    
  const apiKey ='YOUR_RAPID_API_KEY';
  Map<String, String> queryParams = {
    'location': placeInput.text,
    'checkin': checkinDate.text,
    'checkout': checkoutDate.text,
    'adults': adults.text,
  };
  
  Map<String, String> headers = {
    'X-RapidAPI-Key': apiKey,
    'X-RapidAPI-Host': 'airbnb13.p.rapidapi.com'
  };
  
  try {
    var response = await http.get(
      Uri.parse(url).replace(queryParameters: queryParams),
      headers: headers,
    );
      parsedResponse = jsonDecode(response.body);
      setState(() {
        hotelsList = parsedResponse?['results'];
        print(hotelsList);
      });
      
  } catch (error) {
    print('Error: $error');
  }
}


Step 7: Add GridView Widget

Upon receiving the hotel data, the app updates the hotelsList variable and triggers a UI update via setState(). This list is then displayed as a grid of hotel cards, each showing the hotel name, images, number of beds and bathrooms, and a star rating using GridView.builder() and Card widgets.

Dart




hotelsList.length>1 ? Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
         child: GridView.builder(  
              itemCount: hotelsList.length,  
            
           gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(  
                  crossAxisCount: 2,  
                  crossAxisSpacing: 4.0,  
                  mainAxisSpacing: 4.0  
              ),  
          scrollDirection: Axis.vertical,
                itemBuilder: (BuildContext context, int index){
                  return Card(
                     
                     
                    child: Column(
                      children: [
                        SizedBox(height: 10,),
                 Image.network(
                        
                        
                      hotelsList[index]['images'][0],height: 100.0),
                      SizedBox(height: 10,),
               Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Text(hotelsList[index]['name'],style: TextStyle(fontWeight: FontWeight.bold),),
                      )
                      ,
                      SizedBox(height: 10),
                     Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                         Text("????️ "+hotelsList[index]['beds'].toString()),
                         SizedBox(width: 10),
                      Text("???? "+hotelsList[index]['bathrooms'].toString() ),
                      ],
                     ),
                      SizedBox(height: 20,),
                    RatingBarIndicator(
                  rating: hotelsList[index]['rating'].toDouble(),
                  itemBuilder: (context, index) => Icon(
                     Icons.star,
                    color: Colors.amber,
                  ),
                  itemCount: 5,
                  itemSize: 16.0,
                  unratedColor: Colors.amber.withAlpha(50),
                  direction: Axis.horizontal,
                ),
                      ],
                    )
                  );
              }),
       ) :Padding(
         padding: const EdgeInsets.all(100.0),
         child: Center(child: Text("????Get Hotels List on your finger Tips!",style: TextStyle(fontSize: 20,fontWeight: FontWeight.w600),)),
       )


Complete Source Code

Dart




import 'dart:convert';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
  
void main() {
  runApp(const MyApp());
}
  
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gfg Dreambnb',
     theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      debugShowCheckedModeBanner: false,
      home: RailwayTrackingHome(),
    );
  }
}
  
  
  
class RailwayTrackingHome extends StatefulWidget {
  @override
  State<RailwayTrackingHome> createState() => _RailwayTrackingHomeState();
}
  
class _RailwayTrackingHomeState extends State  <RailwayTrackingHome> {
  TextEditingController placeInput = TextEditingController();
  TextEditingController checkinDate = TextEditingController();
  TextEditingController checkoutDate = TextEditingController();
  TextEditingController adults = TextEditingController();
  
  var hotelsList = [];
  Map<String, dynamic>? parsedResponse;
_getDetails() async {
  const apiKey ='YOUR_RAPID_API_KEY';
  Map<String, String> queryParams = {
    'location': placeInput.text,
    'checkin': checkinDate.text,
    'checkout': checkoutDate.text,
    'adults': adults.text,
  };
  
  Map<String, String> headers = {
    'X-RapidAPI-Key': apiKey,
    'X-RapidAPI-Host': 'airbnb13.p.rapidapi.com'
  };
  
  try {
    var response = await http.get(
      Uri.parse(url).replace(queryParameters: queryParams),
      headers: headers,
    );
      parsedResponse = jsonDecode(response.body);
      setState(() {
        hotelsList = parsedResponse?['results'];
        print(hotelsList);
      });
      
  } catch (error) {
    print('Error: $error');
  }
}
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text('Gfg Dreambnb'),
        centerTitle: true,
      ),
      body:SingleChildScrollView(
         
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: [
            SizedBox(height: 10),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: placeInput,
                    decoration: InputDecoration(
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20)),
                      labelText: "Destination"),
                  ),
                ),
                SizedBox(width: 10),
                Expanded(child: TextField(
                    controller: checkinDate, // editing controller of this TextField
                    decoration: InputDecoration( 
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20)),
                       
                       labelText: "Checkin Date" // label text of field
                    ),
                    readOnly: true// set it true, so that user will not able to edit text
                    onTap: () async {
                      DateTime? pickedDate = await showDatePicker(
                          context: context, initialDate: DateTime.now(),
                          firstDate: DateTime(2000), // DateTime.now() - not to allow to choose before today.
                          lastDate: DateTime(2101)
                      );
                        
                      if(pickedDate != null ){
                          String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate); 
                           
                          setState(() {
                             checkinDate.text = formattedDate; // set output date to TextField value. 
                          });
                      }else{
                          print("Date is not selected");
                      }
                    },
                 )),
                SizedBox(width: 10),
                Expanded(child: TextField(
                    controller: checkoutDate, // editing controller of this TextField
                    decoration: InputDecoration( 
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20)),
                        // icon of text field
                       labelText: "Checkout Date" // label text of field
                    ),
                    readOnly: true// set it true, so that user will not able to edit text
                    onTap: () async {
                      DateTime? pickedDate = await showDatePicker(
                          context: context, initialDate: DateTime.now(),
                          firstDate: DateTime(2000), // DateTime.now() - not to allow to choose before today.
                          lastDate: DateTime(2101)
                      );
                        
                      if(pickedDate != null ){
                          String formattedDate = DateFormat('yyyy-MM-dd').format(pickedDate); 
                           
                          setState(() {
                             checkoutDate.text = formattedDate; // set output date to TextField value. 
                          });
                      }else{
                          print("Date is not selected");
                      }
                    },
                 )),
                 SizedBox(width: 10),
                 Expanded(
                  child: TextField(
                    controller: adults,
                    decoration: InputDecoration(
                      labelText: "Enter adults",
                      enabledBorder: OutlineInputBorder(
                            borderSide: BorderSide(
                              color: Colors.green,
                                      ),
                            borderRadius: BorderRadius.circular(20))),
                  ),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _getDetails,
                  child: Text("Search"),
                ),
              ],
            ),
            SizedBox(height: 50),
       hotelsList.length>1 ? Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
         child: GridView.builder(  
              itemCount: hotelsList.length,  
            
           gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(  
                  crossAxisCount: 2,  
                  crossAxisSpacing: 4.0,  
                  mainAxisSpacing: 4.0  
              ),  
          scrollDirection: Axis.vertical,
                itemBuilder: (BuildContext context, int index){
                  return Card(
                     
                     
                    child: Column(
                      children: [
                        SizedBox(height: 10,),
                 Image.network(
                        
                        
                      hotelsList[index]['images'][0],height: 100.0),
                      SizedBox(height: 10,),
               Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Text(hotelsList[index]['name'],style: TextStyle(fontWeight: FontWeight.bold),),
                      )
                      ,
                      SizedBox(height: 10),
                     Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                         Text("????️ "+hotelsList[index]['beds'].toString()),
                         SizedBox(width: 10),
                      Text("???? "+hotelsList[index]['bathrooms'].toString() ),
                      ],
                     ),
                      SizedBox(height: 20,),
                    RatingBarIndicator(
                  rating: hotelsList[index]['rating'].toDouble(),
                  itemBuilder: (context, index) => Icon(
                     Icons.star,
                    color: Colors.amber,
                  ),
                  itemCount: 5,
                  itemSize: 16.0,
                  unratedColor: Colors.amber.withAlpha(50),
                  direction: Axis.horizontal,
                ),
                      ],
                    )
                  );
              }),
       ) :Padding(
         padding: const EdgeInsets.all(100.0),
         child: Center(child: Text("????Get Hotels List on your finger Tips!",style: TextStyle(fontSize: 20,fontWeight: FontWeight.w600),)),
       )
          ],
        ),
        )
    );
  }
}


Output:



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads