EBook reader Application brings the library to your fingertips. This application will be the travel partner of every book lover who loves to read books of their choice. The app is developed using Flutter and provider state management. It uses the Google Books API to fetch the data of books. The app allows you to search for the book by its name. The API provides the details of all the books, you can read the description. The app has the functionality to preview the book or download it, as per availability.
Demo Video:
Concepts Covered in the app
- Provider state management.
- API management
Project Structure
Step-by-Step Implementation of EBook reader app
Step 1: Create a new Flutter Application
Create a new Flutter application using the command Prompt. To create a new app, write the following command and run it.
flutter create app_nameTo know more about it refer this article: Creating a Simple Application in Flutter.
Step 2: Adding the Dependency
To add the dependency to the pubspec.yaml file,add provider, url_launcher and http as a dependency in the dependencies part of the pubspec.yaml file, as shown below:
dependencies:
flutter:
sdk: flutter
provider: ^6.1.4
url_launcher: ^6.3.1
http: ^1.3.0
Now run the below command in the terminal.
flutter pub getOr
Run the below command in the terminal.
flutter pub add provider url_launcher httpStep 3: Import dependencies
To use libraries, import all of them in the respective .dart file.
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
Step 4: Working with main.dart
Add the boilerplate code below in main.dart to initialize the ChangeNotifierProvider in the providers list inside the MultiProvider in the main function, and create a basic structure with an MaterialApp.
main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'screens/home_screen.dart';
import 'services/google_books_api.dart';
// Entry point of the Flutter application
void main() {
runApp(const MyApp());
}
// Root widget of the application
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
// Providing dependencies to the widget tree using MultiProvider
providers: [
// Registering GoogleBooksApi as a ChangeNotifier for state management
ChangeNotifierProvider(create: (_) => GoogleBooksApi()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'EBook reader Application',
// Sets the HomeScreen as the initial screen
home: const HomeScreen(),
),
);
}
}
Step 5: Code for google_books_api.dart
An HTTP request is made to the API and the data of matching query is fetched. The UI is updated according to the status code.
google_books_api.dart:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class GoogleBooksApi extends ChangeNotifier {
// List to store fetched books data from the API
List books = [];
// Boolean to indicate whether data is currently being fetched
bool isLoading = false;
// Function to search books based on user query
Future<void> search(String query) async {
// Construct the URL for the Google Books API with the query string
final url =
'https://www.googleapis.com/books/v1/volumes?q=$query';
// Set loading state to true to indicate data fetching has started
isLoading = true;
// Notify listeners (e.g., UI) to update the loading state
notifyListeners();
try {
// Make an HTTP GET request to the Google Books API
final response = await http.get(Uri.parse(url));
// Log the response status code for debugging purposes
print("response status code ${response.statusCode}");
// Check if the request was successful (HTTP status code 200)
if (response.statusCode == 200) {
// Parse the JSON response body
final data = json.decode(response.body);
// Extract the 'items' field from the response, or set to an empty list if null
books = data['items'] ?? [];
} else {
// If the request fails, clear the books list
books = [];
}
} catch (e) {
// Handle any exceptions that occur during the HTTP request
// Clear the books list in case of an error
books = [];
}
// Set loading state to false to indicate data fetching is complete
isLoading = false;
// Notify listeners (e.g., UI) to update the loading state and display results
notifyListeners();
}
}
Step 6: Code for screens/home_screen.dart
This has the UI of the home page. The user can enter the Title of the book in the textfield and click on search icon to fetch the result. ListView represents the scrollable list of available books. The navigation logic fetches the description of the book from the ListView.
home_screen.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../services/google_books_api.dart';
import './book_details.dart';
// HomeScreen is the main screen of the application where users can search for books
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Access the GoogleBooksApi provider
final bookProvider = Provider.of<GoogleBooksApi>(context);
// Controller for the search input field
TextEditingController searchController = TextEditingController();
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
'EBook reader Application',
style: TextStyle(fontSize: 25),
),
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
toolbarHeight: 70,
elevation: 5,
shadowColor: Colors.green[700],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
body: Column(
children: [
const SizedBox(height: 10), // Adds spacing at the top
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
// Binds the controller to the TextField
controller: searchController,
// Triggers search on submission
onSubmitted:
(value) => bookProvider.search(searchController.text),
decoration: InputDecoration(
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Color.fromARGB(255, 3, 131, 29),
),
borderRadius: BorderRadius.circular(10.0),
),
// Placeholder text
labelText: 'Search for books',
labelStyle: TextStyle(color: Color.fromARGB(255, 3, 131, 29)),
suffixIcon: IconButton(
icon: const Icon(
Icons.search,
size: 30,
color: Color.fromARGB(255, 3, 131, 29),
),
onPressed: () {
// Triggers search on button press
bookProvider.search(searchController.text);
},
),
),
),
),
Expanded(
// Checks if data is still loading
child:
bookProvider.isLoading
? const Center(
child: CircularProgressIndicator(
color: Color.fromARGB(255, 3, 131, 29),
),
)
: ListView.builder(
// Number of books to display
itemCount: bookProvider.books.length,
itemBuilder: (context, index) {
// Current book data
final book = bookProvider.books[index];
return ListTile(
// Book title
title: Text(
book['volumeInfo']['title'] ?? 'No Title',
),
// Authors list
subtitle: Text(
book['volumeInfo']['authors'] != null
? book['volumeInfo']['authors'].join(', ')
: 'Unknown Author', // Fallback if no authors are available
),
onTap: () {
// Navigate to the BookDetail screen when a book is tapped
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BookDetail(book: book),
),
);
},
);
},
),
),
],
),
);
}
}
Step 7: Code for screens/book_detail.dart
This page fetches the details of the book from the Google Books API. It fetches the preview link and download link of the book using the API. It has UI to present the cover of the book.
Note : Since we are using a free API, the cover image and download feature may not properly work. The code below handles the API failure.
book_details.dart:
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class BookDetail extends StatelessWidget {
final Map book;
const BookDetail({Key? key, required this.book}) : super(key: key);
// Function to open URLs in the browser
Future<void> _launchURL(String url) async {
try {
// Encode the URL to handle special characters
final Uri uri = Uri.parse(url);
if (!await launchUrl(uri)) {
throw Exception('Could not launch $uri');
}
} catch (e) {
// Show a snackbar with the error message
debugPrint('Error launching URL: $e');
// You might want to show this error to the user through a SnackBar
// or other UI element
}
}
@override
Widget build(BuildContext context) {
final volumeInfo = book['volumeInfo'];
// Extract URLs
final thumbnail =
volumeInfo['imageLinks'] != null
? volumeInfo['imageLinks']['thumbnail']
: null;
final previewLink = volumeInfo['previewLink'];
final downloadLink =
book['accessInfo'] != null
? book['accessInfo']['pdf'] != null &&
book['accessInfo']['pdf']['isAvailable']
? book['accessInfo']['pdf']['acsTokenLink']
: null
: null;
final buyLink = volumeInfo['infoLink'];
return Scaffold(
appBar: AppBar(title: Text(volumeInfo['title'] ?? 'No Title')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Display the image with a placeholder and error handling
thumbnail != null
? Center(
child: Image.network(
thumbnail,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.error, size: 100);
},
),
)
: const Icon(Icons.book, size: 100),
const SizedBox(height: 16),
Text(
volumeInfo['title'] ?? 'No Title',
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(
'Author(s): ' +
(volumeInfo['authors'] != null
? volumeInfo['authors'].join(', ')
: 'Unknown'),
),
const SizedBox(height: 8),
Text(volumeInfo['description'] ?? 'No Description'),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Preview Button
if (previewLink != null)
ElevatedButton(
onPressed: () {
_launchURL(previewLink);
},
child: const Text(
'Preview',
style: TextStyle(
fontSize: 10,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.only(
right: 20,
left: 25,
top: 15,
bottom: 15,
),
),
),
const SizedBox(width: 10),
// Download Button
if (downloadLink != null)
ElevatedButton(
onPressed: () {
_launchURL(downloadLink);
},
child: const Text(
'Download',
style: TextStyle(
fontSize: 10,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.only(
right: 20,
left: 25,
top: 15,
bottom: 15,
),
),
)
else
ElevatedButton(
onPressed: () {
_launchURL(buyLink);
},
child: const Text(
'More Info',
style: TextStyle(
fontSize: 10,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
padding: EdgeInsets.only(
right: 20,
left: 25,
top: 15,
bottom: 15,
),
),
),
],
),
],
),
),
);
}
}
Step 8: Run the application
Save the project and enter below command to run the application.
flutter runClick Here to get the Full Application Code Access