flutter etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
flutter etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

How to Create Custom Bottom Navigation Bar in Flutter?

 

Step-by-Step Explanation from Scratch



Where does a user who opens your app look first? Usually at the bottom of the screen. That’s why the Bottom Navigation Bar is at the heart of the user experience. But the default bar is not enough. If you want an experience that’s unique, memorable and your own, a custom bar is a must.

In this article, I’ll explain in plain and simple language how you can write your own custom bottom navigation bar in Flutter from scratch. Let’s get started if you’re ready.

1. Preparation: What will we do?

  • An application with four tabs
  • Each tab will have its own screen

Buttons in the submenu:

  • Will be shown in a different color if selected
  • Will change the page when clicked
  • Will be animated

2. Project Structure

Our home page file:

main.dart

Our page files:

screens/
home_screen.dart
search_screen.dart
profile_screen.dart
settings_screen.dart
widgets/
custom_nav_bar.dart

3. Main Structure: Page Switching with Stateful Widget

First, let’s set up the basic structure in main.dart that will manage the switching of tabs:

import 'package:custom_nav_bar/widgets/custom_nav_bar.dart';
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
import 'screens/search_screen.dart';
import 'screens/profile_screen.dart';
import 'screens/settings_screen.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom BottomNav Demo',
debugShowCheckedModeBanner: false,
home: MainScreen(),
);
}
}

class MainScreen extends StatefulWidget {
const MainScreen({super.key});

@override
State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
int currentIndex = 0;

final screens = [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
SettingsScreen(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: screens[currentIndex],
bottomNavigationBar: CustomBottomNavBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
),
);
}
}

4. Let’s Write Custom BottomNavigationBar.

Let’s create a widget calledCustomBottomNavBar in the widgets folder:

import 'package:flutter/material.dart';

class CustomBottomNavBar extends StatelessWidget {
final int currentIndex;
final Function(int) onTap;

const CustomBottomNavBar({
required this.currentIndex,
required this.onTap,
});

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 10),
],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(icon: Icons.home, index: 0, label: 'Ana Sayfa'),
_buildNavItem(icon: Icons.search, index: 1, label: 'Ara'),
_buildNavItem(icon: Icons.person, index: 2, label: 'Profil'),
_buildNavItem(icon: Icons.settings, index: 3, label: 'Ayarlar'),
],
),
);
}

Widget _buildNavItem({
required IconData icon,
required int index,
required String label,
}) {
final isSelected = index == currentIndex;

return GestureDetector(
onTap: () => onTap(index),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedContainer(
duration: Duration(milliseconds: 250),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected ? Colors.blue.shade100 : Colors.transparent,
shape: BoxShape.circle,
),
child: Icon(
icon,
color: isSelected ? Colors.blue : Colors.grey,
size: isSelected ? 28 : 24,
),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 12,
color: isSelected ? Colors.blue : Colors.grey,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
],
),
);
}
}

5. Add Page Instances

A simple scaffold structure for each screen is enough:

home_screen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: Center(
child: Text('Ana Sayfa'),
),
);
}
}

search_screen.dart

import 'package:flutter/material.dart';

class SearchScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent[100],
body: Center(
child: Text('Arama Sayfası'),
),
);
}
}

profile_screen.dart

import 'package:flutter/material.dart';

class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber[100],
body: Center(
child: Text('Profil Sayfası'),
),
);
}
}

settings_screen.dart

import 'package:flutter/material.dart';

class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.purpleAccent[100],
body: Center(child: Text('Ayarlar Sayfası')),
);
}
}

And The Result

Now your app has a simple but powerful bottom navigation bar that reflects your own style.

You can color and shape it however you like, change the icons, or add notification badges to the buttons.

Writing your own custom widget is one of the most enjoyable ways to learn Flutter in depth. The look is yours and the user experience makes all the difference.



I hope it was useful.

Don’t forget to clap and subscribe for more.

Thank you.

Selin.

Flutter’da Custom Bottom Navigation Bar Nasıl Oluşturulur?

 

Sıfırdan Adım Adım Anlatım


Uygulamanı açan bir kullanıcı ilk olarak nereye bakar? Genelde ekranın en altına. İşte tam bu yüzden Bottom Navigation Bar, kullanıcı deneyiminin kalbinde yer alır. Ama varsayılan bar yetmez. Özgün, akılda kalıcı ve sana ait bir deneyim istiyorsan custom bar şart.

Bu yazıda, Flutter’da kendi özel alt gezinme çubuğunu (Custom Bottom Navigation Bar) nasıl sıfırdan yazabileceğini sade ve anlaşılır bir dille anlatıyorum. Hazırsan başlayalım.

1. Hazırlık: Neyi Yapacağız?

  • Dört sekmeli bir uygulama
  • Her sekmenin kendine ait ekranı olacak

Alt menüdeki butonlar:

  • Seçili ise farklı bir renkte gösterilecek
  • Tıklanınca sayfa değişecek
  • Animasyonla canlandırılacak

2. Proje Yapısı

Ana sayfa dosyamız:

main.dart

Sayfa dosyalarımız:

screens/
home_screen.dart
search_screen.dart
profile_screen.dart
settings_screen.dart
widgets/
custom_nav_bar.dart

3. Ana Yapı: Stateful Widget ile Sayfa Değişimi

Önce main.dart içinde tabların geçişini yönetecek olan temel yapıyı kuralım:

import 'package:custom_nav_bar/widgets/custom_nav_bar.dart';
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
import 'screens/search_screen.dart';
import 'screens/profile_screen.dart';
import 'screens/settings_screen.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Custom BottomNav Demo',
debugShowCheckedModeBanner: false,
home: MainScreen(),
);
}
}

class MainScreen extends StatefulWidget {
const MainScreen({super.key});

@override
State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
int currentIndex = 0;

final screens = [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
SettingsScreen(),
];

@override
Widget build(BuildContext context) {
return Scaffold(
body: screens[currentIndex],
bottomNavigationBar: CustomBottomNavBar(
currentIndex: currentIndex,
onTap: (index) {
setState(() {
currentIndex = index;
});
},
),
);
}
}

4. Custom BottomNavigationBar’ı Yazalım

widgets klasörü içerisineCustomBottomNavBar adında bir widget oluşturalım:

import 'package:flutter/material.dart';

class CustomBottomNavBar extends StatelessWidget {
final int currentIndex;
final Function(int) onTap;

const CustomBottomNavBar({
required this.currentIndex,
required this.onTap,
});

@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 10),
],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildNavItem(icon: Icons.home, index: 0, label: 'Ana Sayfa'),
_buildNavItem(icon: Icons.search, index: 1, label: 'Ara'),
_buildNavItem(icon: Icons.person, index: 2, label: 'Profil'),
_buildNavItem(icon: Icons.settings, index: 3, label: 'Ayarlar'),
],
),
);
}

Widget _buildNavItem({
required IconData icon,
required int index,
required String label,
}) {
final isSelected = index == currentIndex;

return GestureDetector(
onTap: () => onTap(index),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedContainer(
duration: Duration(milliseconds: 250),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelected ? Colors.blue.shade100 : Colors.transparent,
shape: BoxShape.circle,
),
child: Icon(
icon,
color: isSelected ? Colors.blue : Colors.grey,
size: isSelected ? 28 : 24,
),
),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 12,
color: isSelected ? Colors.blue : Colors.grey,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
],
),
);
}
}

5. Sayfa Örneklerini Ekleyelim

Her ekran için basit bir scaffold yapısı yeterli:

home_screen.dart

import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: Center(
child: Text('Ana Sayfa'),
),
);
}
}

search_screen.dart

import 'package:flutter/material.dart';

class SearchScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent[100],
body: Center(
child: Text('Arama Sayfası'),
),
);
}
}

profile_screen.dart

import 'package:flutter/material.dart';

class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.amber[100],
body: Center(
child: Text('Profil Sayfası'),
),
);
}
}

settings_screen.dart

import 'package:flutter/material.dart';

class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.purpleAccent[100],
body: Center(child: Text('Ayarlar Sayfası')),
);
}
}

Ve Sonuç

Artık uygulamanda kendi tarzını yansıtan, sade ama güçlü bir alt gezinme çubuğu var.
Bu yapıyı dilediğin gibi renklendirebilir, şekillendirebilir, ikonları değiştirebilir ya da butonlara bildirim rozetleri ekleyebilirsin.

Kendi custom widget’ını yazmak, Flutter’ı derinlemesine öğrenmenin en keyifli yollarından biri. Hem görünüm senin olur, hem de kullanıcı deneyimi fark yaratır.



Umarım faydalı olmuştur.

Daha fazlası için abone olmayı unutmayın.

Teşekkürler.

Selin.

SharedPreferences or Hive? Find the Best Option for Your Flutter App

Comparative analysis of SharedPreferences and Hive usage, performance, structure and ease of use.


One of the most common needs we encounter while developing mobile applications with Flutter is data storage. We need to store small data from the user’s theme preference, session information, last page visited, favorite content, etc. within the application.

The two structures we hear the most at this point:
- SharedPreferences
- Hive

So what is the difference between the two?
Why do some developers use Hive instead of SharedPreferences?
Which is the better choice for your project?

In this article, we will compare these two structures in both their technical and practical aspects.
And with simple examples of their use, you will clearly see which one should be preferred where. Here’s my goal:
By the end of the article, you will have a thorough understanding of SharedPreferences and Hive and will be able to make the right decision for your project.

If you’re ready, let’s get started!

What is SharedPreferences?

SharedPreferences is a framework used in Flutter apps to store simple and small data persistently on the device. It uses the NSDefaults and SharedPreferences APIs, which are natively supported on Android and iOS. This structure is generally preferred for simple operations such as keeping user preferences or session information.

Only certain data types can be stored with SharedPreferences. These are usually primitive data types:

  • String
  • int
  • double
  • bool
  • List<String>

The data is stored in key-value logic. Even if the application is closed, this data is not deleted, it remains unless the user manually cleans the application.

When should it be used?

  • User’s theme preference (e.g. dark mode)
  • Storing information on whether the user is logged in or not
  • User’s last username
  • Settings such as language preference

Advantages

  • Very simple to install and use
  • Sufficient for in-app settings data
  • Wide platform support

Limitations

  • Can only store basic data types
  • Cannot directly save complex data structures (e.g. model class in list)
  • Performance is not suitable for large data structures
  • Data is stored as native, not JSON, and extensibility is limited

Example Usage

dependencies:
shared_preferences: ^2.5.3. //version number may vary
final prefs = await SharedPreferences.getInstance();

// Saving Data
await prefs.setBool('isLoggedIn', true);

// Reading Data
final isLoggedIn = prefs.getBool('isLoggedIn') ?? false;

SharedPreferences is a very simple solution, but it has its limitations. It may be insufficient when more complex data structures are required. In such a case, it is necessary to turn to alternatives. This is where Hive comes in.

What is Hive?

Hive is a lightweight and high-performance NoSQL database developed for Flutter. It is fully compatible with Flutter and is a powerful alternative for developers who want to store local data, especially in mobile apps.

While SharedPreferences only stores simple data types, with Hive it is possible to easily store both these simple types and more complex object structures (such as model classes). One of the biggest advantages of Hive is the combination of type safety and performance.

Data is stored in the device’s file system. However, these files are managed by Hive, not by the developer. Hive stores data in boxes and these boxes work with a key-value system.

When to use it?

  • If complex data structures (e.g. recipes, product lists, user profiles) are to be stored
  • If working with large amounts of local data
  • If fast read/write operations are important
  • If developing applications that will run offline

Advantages

  • High performance
  • Model classes and complex data structures are supported
  • Type-safe
  • Suitable for offline-first applications
  • Easy to test in code

Limitations

  • Initial setup and configuration is a bit more complex than SharedPreferences
  • It is necessary to write an adapter to save model classes
  • Does not work in JSON format, uses its own binary data format
  • Additional configuration may be required if data encryption is desired

Example Usage:

Setup:

dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0 //Version numbers may vary
dev_dependencies:
hive_generator: ^2.0.0
build_runner: ^2.3.3. //Version numbers may vary

Model definition:

import 'package:hive/hive.dart';

part 'user.g.dart';

@HiveType(typeId: 0)
class User extends HiveObject {
@HiveField(0)
String name;

@HiveField(1)
int age;

User({required this.name, required this.age});
}

Saving and Reading Data:

var box = await Hive.openBox<User>('users');

final user = User(name: 'John', age: 25);
await box.add(user);

final storedUser = box.getAt(0);
print(storedUser?.name);

Hive provides great convenience, especially in scenarios where data is structured. Since you can store your own objects, it makes your code more readable and maintainable.

SharedPreferences vs Hive: Which is Which in Which Situation?

When you need to store data in Flutter, there are some criteria to consider when choosing between SharedPreferences and Hive. Below you can find the differences between the two structures under certain headings:

  1. Data Type Support
    - SharedPreferences only supports simple data types such as String, int, bool, double, List<String>.
    - Hive supports both these simple data types and model classes (e.g. User, Product).
  2. Performance
    - SharedPreferences is fast for small amounts of data but can be slow for large amounts of data and frequent access.
    - Hive is high performance and fast even when working with large data sets.
  3. Ease of Configuration and Use
    - SharedPreferences is very simple to install and use. No extra configuration is required.
    - Hive is a bit more complex to set up. If the model is to be stored, it is necessary to create an adapter.
  4. Offline Use and Database Features
    - SharedPreferences is more suitable for simple data such as user settings.
    - Hive can be used as a database in offline applications.
  5. File Structure and Storage
    - SharedPreferences stores data in platform specific file systems using XML or similar structure.
    - Hive uses its own binary format, which saves space and provides speed.
  6. Type Safety
    - SharedPreferences works with dynamic types. Type checking should be done when retrieving data.
    - Hive offers type safety. It is safer to work with model objects.
  7. Encryption and Security
    - SharedPreferences stores data in plain text, open unless additional security measures are taken.
    - Hive can store data encrypted (with additional configuration).
  8. Updating and Maintenance
    - SharedPreferences is stable but limited because it is a long-used structure.
    - Hive is a newer and actively developed package, with strong community support.

Which one should be used in which situation?

Scenario 1: Storing Theme Preference (Light/Dark Mode)
Did the user last use the app in light or dark mode? You want to save this preference and launch it accordingly when the app is reopened.

SharedPreferences is ideal for this scenario. Simple, fast and sufficient if only a bool value is to be stored.

Scenario 2: User Session Retention
After a user logs in, you don’t want the app to show them the login screen unless they log out. You need to save the session state.

Again SharedPreferences is sufficient. If the session information is a simple bool or token value, it can be easily stored.

Scenario 3: Recipe List or Object Storage
You want to store recipes or notes created by the user in the app on the device. Recipes have fields like title, content, score. They are all stored in a model class.

In this case, using Hive would be the right choice. Because SharedPreferences cannot store objects in a list, but with Hive you can define model classes and store them directly.

Scenario 4: Offline Application
You want users to access the content in the application even without a data connection. For example, in a baby recipes app, you want the data to work offline.

Hive is more suitable in this case. Thanks to its NoSQL structure, it can read big data quickly and is suitable for offline-first architecture.

Scenario 5: Storing User’s Favorite Items
User selects multiple products/favorites. This data can be a simple list or a list of IDs.

If the data is not complex, SharedPreferences can be used. However, if there is other information about each item (such as name, category, date), Hive would be more appropriate.

Scenario 6: How to tell if the application was opened for the first time
Show onboarding when the application is opened for the first time and skip it on subsequent ones.

SharedPreferences is sufficient for such simple cases. The first time the application is opened can be checked with a single boolean value.

It is best to make a decision based on the data structure and amount of data you need in each scenario. In complex data structures, when the application needs to work offline or if performance is critical, Hive offers a more powerful solution in the long run.

What to Consider When Moving from SharedPreferences to Hive?

You may have used SharedPreferences when you first developed your application. However, over time, you may realize that the data structures have become more complex and that this structure is limiting you. In this case, switching to Hive can be a good solution. But there are some important points to consider when making the transition:

  1. Design the Data Model
    To store object-based data with Hive, you must first create model classes. These classes are marked with @HiveType and @HiveFieldanotations. You also need to write an adapter for each model. This structure ensures that the data structure is clear and consistent, but requires some initial preparation.
  2. Using build_runner and hive_generator
    To automatically generate Hive adapters, you need to add the build_runner and hive_generator packages to your project. During code generation, it is necessary to pay attention to file naming and make correct part definitions.
  3. Transferring Old Data (Optional)
    If you want to move data from SharedPreferences to Hive, you can write a migration code that will take this data and write it to Hive boxes when your application first runs. For example:
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');

final box = await Hive.openBox('settings');
await box.put('auth_token', token);

// Delete the old data
await prefs.remove('auth_token');

You can run this migration step only once, and then you can remove SharedPreferences completely.

  1. Using Hive.init or Hive.initFlutter
    Before using Hive, you must initialize it. In mobile apps, Hive.initFlutter() is usually used and this code should be written in the main() function before the runApp() call.
  2. Working with Async Boxes
    Hive boxes are opened async. Therefore, it is necessary to check that the boxes are ready in the state management of the application or with structures such as FutureBuilder.
  3. Planning the Database Structure in Advance
    Since Hive is not SQL based, it should be used by planning the data structure well, not by thinking about data relationships. In other words, it should be considered as a document-based structure, not a relational one.
  4. Impact on Application Size
    Hive comes with some extra dependencies. There may be a small increase in the size of the application. However, this is usually offset by performance and flexibility gains.

If you plan the migration correctly and set up the data structure that fits the needs of the application, using Hive offers a much more sustainable solution.

When to Use Which Tool in a Nutshell?

When developing applications with Flutter, data storage is an area that becomes more important as the project progresses. SharedPreferences, which offers a quick solution for small needs at first, may be sufficient in many cases. However, when data structures become complex, flexibility and performance are required, or when you want to build a more advanced structure that will work offline, Hive becomes a much more powerful solution.

In this article, I’ve tried to examine in detail what both frameworks do, how they work and in which situations they should be used. It is always the healthiest way to analyze the needs of the application correctly and choose the framework that is most suitable for long-term maintenance and development.

If you are still having a hard time deciding, starting with SharedPreferences in small projects and switching to Hive when the need grows is also a viable method. The important thing is to really understand the structure you are using and use it in the right place.

Thank you for reading this far. I hope it was a comprehensive comparison.

Don’t forget to press clap if you liked my article and follow me to be informed about my other content.

Thank you very much.

Selin.