8 Şubat 2022 Salı

Flutter Easy Localization Paketi ile Çoklu Dil Desteği Eklenmesi

 Merhaba arkadaşlar todo uygulaması üzerinde çoklu dil desteğini nasıl  yapabiliriz bununla alakalı paketi uygulama da nasıl kullanabiliriz hakkında bir yazı yazalım istedim.


Paketimiz Easy_localization

Link: https://pub.dev/packages/easy_localization

Paket olarak arkadaşlar zaten readme kısımlarını ve installing kısımlarını projeye nasıl entegre ederiz çok güzel açıklamışlar ama oldu ya kısmen bilgi olması açısından bizde dinamik varsayılan alanları nasıl gösterebiliriz bunun hakkında kısa bir yazı yazalım.

Arkadaşlar öncelikle ctrl+shift +p diyerek add dependency diyerek easy_localization paketini en son ki versiyonunu indiriyoruz.

veya

pubspec.yaml dosyasında bu paketi installing seçeneklerinde ki gibi ekleyip  paketi download ediyoruz.

dependencies:
  easy_localization: <last_version>

Sonra  bizden paket folder(klasör) dizinimize assets diye bir klasör açmamızı ve içerisine translations klasörünün içerisine .json dosyalarını eklemimizi istiyor.

  /asserts/translations / => en-US.json veya tr-TR.json

assets
└── translations
    ├── {languageCode}.{ext}                  //only language code
    └── {languageCode}-{countryCode}.{ext}    //or full locale code

Aynı paket ios tarafında localization çalıştırabilmek için  ios/Runner/Info.plist paketi içerisine seçili  kod satırlarını ekleyip kaydetmemizi istiyor.

<key>CFBundleLocalizations</key>
<array>
	<string>en</string>
	<string>nb</string>
</array>

Easy_localization paket olarak main methodunu başlangıç olarak  beni Initialize et diyor. Main kodları arasına bu kodlarımızı da ekliyoruz sonrasında MaterialApp Localization nesnesi ile sarmalıyoruz.

 WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();

MaterialApp sarmalamak içinse kodlarımız aynı şekilde document sayfasında mevcut bunlar;

 return MaterialApp(
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      home: MyHomePage()
    );

Paketimizi ekledik Material App sarmaladık başlangıçta iniatlize de ettik peki sonrasında ne yapıyoruz. Bu sefer Translations klasörü içerisinde bulunan  en-US.json ve tr-Tr.json dosyalarımızın içerisine json formatında statik verilerimizi giriyoruz.



Json dosyalarımızın içerisini de statik olarak doldurduktan sonra MaterialUI içerisinde .tr() ekleyerek local cihaz bilgisine göre güncelleme işlemini tamamlamış oluyoruz.

Mesela TextField için hintText alanını nasıl local dil bilgisini ekledik.

 TextField(
              autofocus: true,
              style: TextStyle(fontSize: 18),
              decoration: InputDecoration(hintText: 'add_Task'.tr()),
              onSubmitted: (value) {}

Burada text alanı dışında tanımlanan .tr() etiketi Türkçe'den gelen  tr olmuyor buna dikkat edelim. Ayrıca tr-Tr ve en-US dosyalarında ki başlık alanları aynı isimde olması gerektiğini unutmayalım !!

Ayrıca biz  todo uygulamasında hazır paketlerden datetime_picker paketininde local dilini değiştirmek içinde paket içerisinde bulunan locale: bilgisine yeni method tanımlayarak cihaz bilgisini alarak paketi de istenilen dile ulaşmasını sağlayalım.

helper diye ana dizine yenir bir klasör tanımlıyoruz. Klasör altına translation_helper.dart class içerisine local cihaz dil bilgisini alacak bir method oluşturup bunu return eden kodları aşağıya bırakıyorum.

class TranslationHelper {
  TranslationHelper._();

  static getDeviceLanguageBul(BuildContext context) {
    var deviceLanguage = context.deviceLocale.countryCode;
    switch (deviceLanguage) {
      case 'tr':
        return LocaleType.tr;
      case 'en':
        return LocaleType.en;
    }
  }
}

Context nesnesi olmak zorunda olduğunu gözden kaçırmayalım. Çünkü context sayesinde local cihaz bilgisini alıyoruz.

  var deviceLanguage = context.deviceLocale.countryCode;

Bu sayede countryCode bilgisini de return etmiş hazır paket için local dil bilgisini almış bulunmaktayız.

locale: TranslationHelper.getDeviceLanguageBul(context),

Kod bloğunu da context içerisine entegre etmiş bulunmaktayız.

Dil paketini Türkçe seçtikten sonraki ekran görüntüleri;


Dil paketini Ingilizce seçtikten sonraki ekran görüntüleri;



Evet arkadaşlar Easy_localization paketini kullanarak çoklu dil desteğini statik nesneler üzerinde gerçekleştirmiş olduk. Tabi bunu paket üzerinde ki local bilgisine dayalı olarak  güncellemeler yaparak projeyi daha da büyütme şansınız var ama temelinde böyle bir şey olduğunu anlatmak istedim.

Umarım faydalı bir içerik olmuştur. Keyifli okumalar ve iyi kodlamalar arkadaşlar.

Sağlıcakla kalın..


7 Şubat 2022 Pazartesi

Flutter Todo Uygulaması üzerinde Search özelliği oluşturmak

 Merhabalar arkadaşlar;

 Flutter serisine devam ediyoruz. Önceki yazımızda sizlere Flutter Hive Depolama yöntemi anlatmıştık.

 Aynı proje üzerinde bu sefer liste içerisinde search(Arama) özelliği ile belirtilen görev(task) var mı yok mu kontrolü sağlamak ve geçerli görev(task) üzerinde normal crud işlemlerini nasıl yaparız ona değineceğiz. 


Return ettiğimiz Scaffold nesnesi  içerisinde appbar: Appbar içerisinde liste olarak [] iconButton nesneleri gönderiyoruz. Iconbutton üzerinde wrap widgets diyerek gestureDetector ile onTab özelliğini aktif ediyoruz. Yani kısaca kod ekranını paylaşırsak;

appBar: AppBar(
        title: GestureDetector(
          onTap: () {
            _showADdTaskBottomSheet(context);
          },
          child: const Text(
            'Genmot Görev Takip Uygulaması',
            style: TextStyle(color: Colors.black),
          ),
        ),
        centerTitle: false,
        actions: [
          IconButton(
            onPressed: () {
              _showSearchPage();
            },
            icon: Icon(Icons.search),
          ),
          IconButton(
            onPressed: () {
              _showADdTaskBottomSheet(context);
            },
            icon: Icon(Icons.add),
          ),
        ],
      ),

IconButton'un onpressed  özelliğinin içerisine _showSearchPage diye bir local method tanımlıyoruz. Bu method sayesinde bize flutter tarafından showSearch hazır methodunu kullanıma sade bir kod bloğu sağlamış oluyoruz.

 _showSearchPage() async {
    await showSearch(
        context: context, delegate: CustomSearchDelegate(allTask: _allTask));
    _getAllTaskDb();
  }

Bu kod bloğunda  showSearch ile CustomSearchDelegate methoduna çağrışım da bulunuyoruz. Bu asenkron çalışması gereken bir method olduğu için async ve await eklemeyi unutmuyoruz arkadaşlar. Widgets klasörümüze custom_delegate.dart diye yeni bir class oluşturuyoruz ve bu class nesnesini SearchDelegate nesnesinden extends ediyoruz.

class CustomSearchDelegate extends SearchDelegate {}


Klasör ağaç yapımız da bu şekilde olduktan sonra CustomSearchDelegate class extends sonrası build edilmesi gereken 4 method ile beraber geliyor arkadaşlar bunları da doldurtuktan sonra güzel bir search butonuna sahip olacağız.

Peki 4 adet build edilecek olan methodlar neler onları kısaca açıklayalım.

1. Build Actions;

  @override
  List<Widget>? buildActions(BuildContext context) {
    // TODO: implement buildActions
    //Arama kısmında ki sağ taraftaki iconları kontrol etmek için kullanılıyor.
    return [
      IconButton(
          onPressed: () {
            query.isEmpty ? null : query = '';
          },
          icon: Icon(Icons.clear))
    ];
  }

Burası arkadaşlar arama kısmında görünecek butonların  veya iconların olduğu alandır.

2. Build Leading;

  @override
  Widget? buildLeading(BuildContext context) {
    // TODO: implement buildLeading
    // en baştaki iconları
    return GestureDetector(
      onTap: () {
        close(context, null);
      },
      child: Icon(
        Icons.arrow_back_ios,
        color: Colors.black,
        size: 24,
      ),
    );
  }

Burası da icon veya button olarak geri işlemleri için uygulama yapılacak alanı işaret ediyor. Kod bloğunda da yaptığımız gibi arrow_back_ios diyerek geri button işareti sağlamış oluyoruz. Material düzeninize bağlı olarak icon renklendirmesi de yapabilirsiniz. O sizin tasarım zevkinize bağlıdır.

3. Build Result

@override
  Widget buildResults(BuildContext context) {}

Arama sonucunda gösterim yapılacak methodları buraya entegre ediyoruz. Arama yapıldıktan sonra geriye dönecek widgets burada return ederek material tasarımımızı tamamlamış oluyoruz.

4. Build Suggestions

 @override
  Widget buildSuggestions(BuildContext context) {}

Arama konusunda daha hassas bir yapı oluşturmak için bu alanda harf duyarlılığı üzerinde arama sonuçlarını göstermek istersek bu alanda return ettiğimiz yapıları buraya entegre ediyoruz.

Buraya kadar ki kısım SearchDelegate bize sağladığı kolaylıkları içeriyordu ve crud işlemleri için Hive Depolama yönteminde yaptığımız kurucu yapıları return ettiğimiz nesneler içerisinde çağırarak sonuca basit şekilde ulaşacağız arkadaşlar.

Delete Operasyonu için;

Öncelikle global olarak CustomSearchDelegate class'ına ana ekranımızdan çekili olan listeyi göndermek için List<allTask> bekleyecek şekilde tanımlama yapıyoruz. Sonrasında bu alanı required olarak tanımlayarak boş geçilme durumunu ortadan kaldırıyoruz.

final List<Task> allTask;

  CustomSearchDelegate({required this.allTask});

home_page.dart => içerisinde çağırırken local method olarak;

 _showSearchPage() async {
    await showSearch(
        context: context, delegate: CustomSearchDelegate(allTask: _allTask));
    _getAllTaskDb();
  }

Required alanları CustomSearchDelegate iletiyoruz.

    List<Task> filteredList = allTask
        .where((task) => task.name.toLowerCase().contains(query.toLowerCase()))
        .toList();

Sonrasında allTask üzerinde where komutuyla aramalarımızı hepsini lowerCase olacak şekilde ayarlayıp sonucu ekrana liste halinde bastırıyoruz.

Gelelim listelenmiş ve arama sonucunda ekrana gelen görev(task) içerisinde delete işlemi için onDismissed () => içerisinde;

 onDismissed: (direction) async {
                    filteredList.removeAt(index);
                    await locator<LocalStorage>()
                        .deleteTask(task: _oAnkiEleman);
                  },

Main methodu içerisinde singelton olarak tanımladığımız locator nesnesini çok uğraşmadan asenkron olarak çağırarak deleteTask(task: _oankiEleman) ile delete işlemi tamamlamış oluyoruz.

Güncelleme işlemleri için;

task_list_item.dart class içerisinde gerçekleştiriyoruz  bütün süreçlerimizi hem widgets üzerinde geziniyoruz ve bu sayede kod karmaşasından ve stabil çalışan bir uygulama yapmış oluyoruz arkadaşlar.

Zaten burası kendi başına bir stateful widget olduğu için kendi içerisinde süreçleri tekrar build ediyor her setState() ettiğimizde searchDelegate için ayrı bir method tanımlaması yapmamıza gerek kalmadan süreci tamamlamış oluyoruz.

   _localStorage.updateTask(task: widget.task);
            setState(() {});




Evet arkadaşlar bir yazımızın daha sonuna geldik. En son olarak bu projeye Easy location ekleyerek dil yapılandırması yapmak istiyorum. Onu da bu yazı içerisine eklemek istemedim kafa karışıklığı ve hizmet ettiği alan sınırlı olsun diye bir sonraki yazıda görüşmek üzere...

İyi kodlamalar...



Flutter Projelerinde Hive ile Veri Tabanı Yönetimi

 Merhaba arkadaşlar Blogger uygulaması üzerinde yeni yazılar yayınlama kararı aldıktan sonra neler yazarım diye düşünürken Flutter serisi yayınlamak mantıklı geldi. Çünkü bende mobil dünyasına yeni girip öğrendiklerimi öğrenmiş miyim sorgusunu anlatarak öğrenmek isteyenlerdenim.

Her neyse lafın kısası makuldur. Seriye Hive Depolama Yöntemi ile başlayalım arkadaşlar;

Flutter da Depolama olarak bir çok yöntem olduğu  JavaScript de kullandığımız metodolojiye uygun  LocalStorage yapısını kullanacağız. 


Bu tabiki paketler serisi üzerinden gerçekleşecek bir yapı javascript deki gibi kolay bir yapı değil.

Öncelikle kurulum yapılması gereken paketler serisini bugün ihtibariyle sürüm notlarını aşağıda görsel olarak paylaşacağım ve sonrasında paket linklerini pub.dev sitesinde ekleyeceğim.

# The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  google_fonts: ^2.3.0
  flutter_datetime_picker: ^1.5.1
  uuid: ^3.0.5
  intl: ^0.17.0
  hive: ^2.0.5
  hive_flutter: ^1.1.0
  build_runner: ^2.1.7
  hive_generator: ^1.1.2
  get_it: ^7.2.0

Öncelikle kurulu olan paketler bize basit bir todo uygulaması üzerinden hizmet ediyor.

Burada google_fonts ve datetime_picker hepimizin bildiği terimler onları geçiyorum.

uuid: ^3.0.5
  intl: ^0.17.0
  hive: ^2.0.5
  hive_flutter: ^1.1.0
  build_runner: ^2.1.7
  hive_generator: ^1.1.2

intl paketi de datetime picker üzerinden seçilen tarih formatını ayarlamak için kullandığımız bir paket.

hive versiyon olarak güncel tarihte ^2.0.5 üzerinde yayında
paket linki: https://pub.dev/packages/hive

installing durumları hakkında bilgi paylaşmak istemiyorum çünkü bu süreç pub.dev üzerinde zaten mevcut.

hive_flutter versiyon ^1.1.0

Link: https://pub.dev/packages/hive_flutter/install

hive_generator versiyon ^1.1.2

Link: https://pub.dev/packages/hive_generator

ve son olarak da build_runner paketimiz var bunları version linklerini paylaştıktan sonra kısa bir bilgilendirme yaparak küçük bir todo uygulaması yapmış olacağız.

build_runner versiyon ^2.1.7

Link: https://pub.dev/packages/build_runner

Kısaca paketlerimizin pub.dev üzerinde ki durumları bu şekilde.

Model olarak task class'ımız mevcut. Task model'inde kullandığımız alanlarımız id,name,createdAt ve isCompleted alanları;

import 'package:hive/hive.dart';
import 'package:uuid/uuid.dart';
part 'task_model.g.dart';

@HiveType(typeId: 1)
class Task extends HiveObject {
  @HiveField(0)
  final String id;

  @HiveField(1)
  String name;

  @HiveField(2)
  final DateTime createdAt;

  @HiveField(3)
  bool isCompleted;

  Task(
      {required this.id,
      required this.name,
      required this.createdAt,
      required this.isCompleted});

  factory Task.create({required String name, required DateTime createdAt}) {
    return Task(
        id: const Uuid().v1(),
        name: name,
        createdAt: createdAt,
        isCompleted: false);
  }
}

hive paketi ile alanların index numaralarını belirttik ve required tanımlamalarını gerçekleştirdik. Aynı model içerisinde paket sayesinde extends edeceğimiz yeni model ismini de part içerisinde geçerek paketin yeni paket oluşturmasını sağladık.

Tabi bunları yaptıktan sonra proje ayağa kalkarken main methodu içerisinde tanımlamalarımızı da unutmayalım.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:todo_app/data/local_storage.dart';
import 'package:todo_app/pages/home_page.dart';
import 'models/task_model.dart';

final locator = GetIt.instance;

Future<void> setupHive() async {
  await Hive.initFlutter();
  Hive.registerAdapter(TaskAdapter());
  var taskBox = await Hive.openBox<Task>('tasks');
}

void setup() {
  locator.registerSingleton<LocalStorage>(HiveLocalStorage());
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  //status bar kaybetmek için --> saat ve sarj bilgisi olan alan
  SystemChrome.setSystemUIOverlayStyle(
      const SystemUiOverlayStyle(statusBarColor: Colors.transparent));
  await setupHive();
  setup();
  runApp(const MyApp());
}

Main.dart içerisinde setupHive  methodu tanımlayarak Hive ait tanımlamaları içerisinde gerçekleştirdik. Sonrasında yaşam döngüsü olarak kaç kere ayağa kalkmasını istediğimiz durumlara özel singleton tanımalaması yaptık. Burada biz bir kerelik ayağa kalkmış locator nesnesi proje boyunca yeterli olacağı için ekstra başka bir yapı kullanma gereği görmedik.

Tabi bunları yapmadan önce hive paketinin kendi sayfasında generator türetmesini terminal ekranından çalıştırarak TaskAdapter modelini türetmesini yapmamız gerekiyor. Yoksa;

  await Hive.initFlutter();
  Hive.registerAdapter(TaskAdapter());

asenktron olarak Hive Flutter üzerinde  initial ettiğimizde register ederken flutter TaskAdapter nesnesini arayacaktır.

TaskAdapter modelini elde etmek için hive paketinin generator üzerinden sağladığı kolaylık için kendi sitesinde yayınladığı kod

flutter packages pub run build_runner build

Kod bloğunu run ettikten sonra model klasörü içerisinde ;


Bu şekilde bir klasör yapısı ve models içerisinde task_model.g.dart diye sınıfımız oluşmuştur.  Burada dikkat etmemiz gereken durumlardan bir taneside 

  var taskBox = await Hive.openBox<Task>('tasks');

kod bloğunun model olarak kullandığımız local_storage.dart class içerisinde ki 

    _taskBox = Hive.box<Task>('tasks');

yapıyla aynı olması gerekiyor. Şimdi local_storage.dart nesnesi nereden çıktı diyenleri duyar gibiyim. Arkadaşlar bu da bizim sanal sınıfımız içerisinde bulunan Hive methodları için kullandığımız bir abstract classtır.

Olur ya bu yapıyı başka bir yöntem olan SqFlite üzerinde kullanmak isterseniz yapacağınız tek şey Hive üzerinde yaptığımız sanallaştırmayı bu sefer SqFlite üzerinde yapacaksınız.

local_storage.dart classı üzerine gelirsek orada sanal sınıflarımız nelermiş onlara da bakalım.

abstract class LocalStorage {
  Future<void> addTask({required Task task});
  Future<Task?> getTask({required String id});
  Future<List<Task>> getAllTask();
  Future<bool> deleteTask({required Task task});
  Future<Task> updateTask({required Task task});
}

Bu abstract class içerisinde ki methodlar sayesinde hangi yöntem olursa olsun extends ederek o yapıyı rahat bir şekilde projeye entegre etmiş olacağız arkadaşlar.

Hive paketine ait sql sorgularını yazabilmek için yeni local method oluşturup onu extends ediyoruz.

class HiveLocalStorage extends LocalStorage {
  late Box<Task> _taskBox;
  HiveLocalStorage() {
    //main methodunda ki ile aynı olmak zorunda 'tasks'
    _taskBox = Hive.box<Task>('tasks');
  }
  @override
  Future<void> addTask({required Task task}) async {
    await _taskBox.put(task.id, task);
  }

  @override
  Future<bool> deleteTask({required Task task}) async {
    await task.delete();
    return true;
  }

  @override
  Future<List<Task>> getAllTask() async {
    List<Task> _allTask = <Task>[];
    _allTask = _taskBox.values.toList();
    if (_allTask.isNotEmpty) {
      _allTask.sort((Task a, Task b) => a.createdAt.compareTo(b.createdAt));
    }
    return _allTask;
  }

  @override
  Future<Task?> getTask({required String id}) async {
    if (_taskBox.containsKey(id)) {
      return _taskBox.get(id);
    } else {
      return null;
    }
  }

  @override
  Future<Task> updateTask({required Task task}) async {
    await task.save();
    return task;
  }

Burada klasik olan getAllTodo, add,delete ve update durumlarını hive yapısına özel doldurup local_storage kısmını tamamlamış oluyoruz.

Buraya kadar aslında kısaca alt yapıyı oluşturmuş oluyoruz ama görünür de tabi nasıl kullanacağımız ile alakalı bir bilgi paylaşmadık. Hadi şimdi de onu nasıl yapıyoruz anlatalım burada bitirelim.

Add methodu için;

todo eklemesi  yapacağımız zaman, ekleme yapılacak widget veya page üzerinde local de global olarak local_storage.dart nesnesini tanımalaması yaparak başlıyoruz. Hani başta singleton olarak tanımladığımız yöntem aslında buraya hizmet ediyor.

  late List<Task> _allTask;
  late LocalStorage _localStorage;
  @override
  void initState() {
    super.initState();
    _localStorage = locator<LocalStorage>();
    _allTask = <Task>[];
    _getAllTaskDb();

initState içerisinde boş bir List<Task> nesnesi tanımlıyoruz sonrasında late kullanarak LocalStorage tanımlamasını yapıyoruz. _localStorage locator<LocalStorage>() üzerinden türeterek kullanıma açıyoruz singelton olarak.

 if (value.length > 5) {
                  DatePicker.showTimePicker(context, showSecondsColumn: false,
                      onConfirm: (time) async {
                    var yeniEklenecekGorev =
                        Task.create(name: value, createdAt: time);
                    _allTask.add(yeniEklenecekGorev);
                    await _localStorage.addTask(task: yeniEklenecekGorev);
                    setState(() {});
                  });

Validator kontrolü sonrasında confirm olayını gerçekleştirirken de hem sanal listeme hemde hiv ile tanımladığım localstorage alanıma ekleme yapıyorum. Tabi bu methodların hepsi asenktron olacağı için async eklemeyi unutmuyoruz. Herhangi bir bloc provider kullanmadığımız için statefull yapısının bu değişiklikleri algılaması içinde setState(){} eklemeyi unutmuyoruz.

Delete methodu içerisinde aynı türetmeler sonrasında;

 onDismissed: (direction) {
                      _allTask.removeAt(index);
                      _localStorage.deleteTask(task: _oAnkiEleman);
                      setState(() {});
                    },

onDismmised => sonrasında _localStorage.deleteTask(task:_oankieleman) göndererek setState(){} yapıyoruz.

Güncelleme işlemleri içinde;

 widget.task.isCompleted = !widget.task.isCompleted;
            _localStorage.updateTask(task: widget.task);
            setState(() {});

updateTask methodunu sanal sınıfımızdan çağırarak task nesnesini gönderiyoruz. Tabi son olarak listeleme yapacağımız todoların kullanım durumu kaldı.

Onu da kısaca anlatmak gerekirse boş olan task listesi içerisinde initState içerisinde doldurma yapıyoruz. 

Projeye basit todo ekleyerek sonucu ekranda gibi aldık ve herhangi bir problem çıkmadı. Umarım faydalı bir içerik olmuştur. Bu serinin devamı için olumlu olumsuz yorumlarınızı bekliyorum. 

Herkese iyi kodlamalar...