ورود و عضویت
0
سبد خرید شما خالی است
0
سبد خرید شما خالی است

آموزش کار با Supabse در فلاتر جایگزین فایربیس

5 دیدگاه
20 دقیقه برای مطالعه
طراحی اپلیکیشن دیوار

استفاده از پلتفرم های ارائه دهنده خدمات بک اند BaaS باعث کاهش هزینه و افزایش سرعت توسعه اپلیکیشن ها میشود.

با کمک این سیستم ها بدون نیاز به برنامه نویسی سمت سرور میتوانید تمرکز خود را فقط روی طراحی اپلیکیشن های خود بگذارید.

معروف ترین این سرویس ها مربوط به شرکت گوگل به نام فایربیس Firebase است.

اما استفاده از این سرویس در این سالها برای برنامه نویسان ایرانی با مشکلاتی همراه بود.

وجود محدودیت اینترنت ملی و همچنین تحریم کاربران ایرانی بخشی از این مشکلات میباشند.

معرفی جایگزین های فایربیس

بنابراین در این آموزش قصد استفاده از پلتفرمی جایگزین به نام Supabase در فریمورک فلاتر داریم.

Supabase چیست؟

Supabase یک سرویس توسعه داده شده است که به توسعه دهندگان کمک می کند تا برنامه های کاربردی وب و موبایل را با استفاده از پایگاه داده های ارتباطی (Database) و وب سوکت ها (WebSockets) ایجاد کنند.

این سرویس یک جایگزین منبع باز برای Firebase محسوب می شود و مجموعه ای از ابزارها و خدمات را در اختیار توسعه دهندگان قرار می دهد.

Supabase از پایگاه داده PostgreSQL برای ذخیره و مدیریت داده ها استفاده می کند و از آنجا که PostgreSQL یک پایگاه داده قدرتمند و روبسته است، توسعه دهندگان می توانند از قابلیت های پیشرفته مانند رابطه بندی، تراکنش ها و توابع ذخیره شده در پروژه خود بهره ببرند.

استفاده از Supabase در فلاتر

برای استفاده از Supabase در فلاتر، شما می‌توانید از کتابخانه رسمی Supabase Dart استفاده کنید.

این کتابخانه امکاناتی را فراهم می‌کند که به شما اجازه می‌دهد با استفاده از API Supabase ارتباط برقرار کنید و عملیات مانند اضافه کردن، به‌روزرسانی، حذف و دریافت داده‌ها را انجام دهید.

همچنین، شما می‌توانید از وب سوکت‌های Supabase استفاده کنید تا به رویدادها و آپدیت‌های زنده در پایگاه داده گوش کنید.

کتابخانه Supabase Dart مستندات جامعی دارد که شما را در فرآیند اتصال و استفاده از Supabase در برنامه‌های فلاتر راهنمایی می‌کند.

می‌توانید از این مستندات استفاده کنید تا با نحوه استفاده از کتابخانه آشنا شوید و عملکرد Supabase را در برنامه‌های فلاتر خود پیاده‌سازی کنید.

طراحی اپلیکیشن مشابه دیوار در فلاتر

در این آموزش ویدیویی با کمک Supabase یک اپلیکیشن آگهی مشابه وبسایت دیوار با استفاده از فریمورک فلاتر طراحی خواهیم کرد.

پیش نیاز این آموزش آشنایی با Flutter میباشد.

اگر هنوز شروع به یادگیری فلاتر نکرده اید میتوانید از پکیج آموزش فلاتر استفاده کنید.

ایجاد پروژه جدید در Supabase

ابتدا اگر دارای حساب کاربری نیستید یک اکانت جدید در وبسایت Supabase ایجاد کنید.

بعد از ثبت نام و ایجاد پروژه جدید یک رمز عبور نیز برای دیتابیس خود باید ثبت کنید.

پس از انجام این کار وارد بخش Authentication میشویم تا پالیسی های مورد نظر را ثبت کنیم.

از منوی سمت چپ عبارت policiesرا انتخاب کنید و سپس روی گزینه new policy کلیک کنید.

در پنجره باز شده روی گزینه اول Get started quickly کلیک کنید.

روی عبارت Enable read access to everyone کلیک کنید و پالیسی مورد نظر را اضافه کنید.

supabase

سپس برای مرتبه دوم پالیسی Enable insert access for authenticated users only را انتخاب کنید.

پس از انجام این بخش نیاز داریم تا یک جدول در دیتابیس پروژه ایجاد کنیم.

از منوی سمت چپ روی گزینه Table editor کلیک کنید.

در صفحه باز شده دکمه new table را انتخاب کنید.

نام جدول را ads میگذاریم. اگر میخواهید پایگاه داده از قابلیت ارتباط بی درنگ پشتیبانی کند تیک گزینه Enable Realtime را فعال کنید.

جدولی که نیاز داریم ساختاری به شکل زیر خواهد داشت.

ستون userid کلید خارجی میباشد که برای فعال کردن این بخش روی آیکون زنجیر میتوانید کلیک کنید تا کلید های خارجی که نیاز دارید را به جدول اضافه کنید.

supabase فلاتر

بعد از طراحی جدول برای تکمیل شدن این بخش نیاز داریم تا قابلیت ذخیره سازی فایل را هم فعال کنیم.

به همین منظور روی گزینه Storage کلیک کنید و عبارت New bucket را انتخاب کنید.

برای آن یک اسم قرار دهید و نیاز به تنظیمات دیگری نخواهیم داشت.

به عنوان گام نهایی وارد بخش تنظیمات شوید و از آنجا به صفحه API بروید.

در این قسمت Project URL و Project API keys را کپی کنید و پیش خود نگهدارید. برای بخش فلاتر به آنها نیاز خواهیم داشت.

ساخت اپلیکیشن فلاتر

برای شروع کار در فلاتر ابتدا یک پروژه جدید در اندروید استودیو تعریف میکنیم.

برای این آموزش نیاز به اضافه کردن چهار پکیج زیر خواهیم داشت:

  supabase: ^1.6.4
  supabase_flutter: ^1.8.1
  flutter_dotenv: ^5.0.2
  file_picker: ^5.3.0

از پکیج flutter_dotenv برای کار با فایل های env استفاده خواهیم کرد.

برای افزایش امنیت برنامه کلید های خصوصی را در فایل هایی با پسوند env ذخیره میکنیم.

اولین صفحه ای که آن را طراحی میکنیم فایل main میباشد. کدهای زیر را در این فایل قرار دهید:

void main() async{

  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load();
  String supabaseUrl = dotenv.env['SUPABASE_URL']??'';
  String supabaseKey = dotenv.env['SUPABASE_KEY']??'';

  await Supabase.initialize(url: supabaseUrl, anonKey: supabaseKey);
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Supabase',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const FirstPage()
    );
  }
}


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

  @override
  State<FirstPage> createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {


  final SupabaseClient supabaseClient = Supabase.instance.client;
  User? _user;
  @override
  void initState() {
    // TODO: implement initState
    _getAuth();
    super.initState();
  }

  Future<void> _getAuth() async{
    setState(() {
      _user = supabaseClient.auth.currentUser;
    });

    supabaseClient.auth.onAuthStateChange.listen((event) {
      setState(() {
        _user = event.session?.user;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return _user==null ? const AuthPage() : AdsList();
  }
}

در متد main کار مقداردهی Supabase را انجام میدهیم. برای اینکه در صفحات دیگر بتوانیم از این کلاس یک نمونه ایجاد کنیم این کار حتما باید در این قسمت انجام شود.

در متد _getAuth() بررسی میکنیم که آیا کاربر وارد حساب خود شده یا خیر.

با کمک خروجی این متد کاربر را به صفحه AdsListیا AuthPage منتقل میکنیم.

در صفحه AuthPage فرم ثبت نامی طراحی کنیم تا به وسیله آن کاربر بتواند یک حساب جدید ایجاد کند و یا اگر از قبل دارای اکانت میباشد بتواند وارد شود.

class AuthPage extends StatefulWidget {
  const AuthPage({Key? key}) : super(key: key);

  @override
  State<AuthPage> createState() => _AuthPageState();
}

class _AuthPageState extends State<AuthPage> {

  final SupabaseClient supabase = Supabase.instance.client;
  bool _signinLoading = false;
  bool _signUpLoading = false;

  final _email_controller = TextEditingController();
  final _passwodrd_controller = TextEditingController();
  final _formKey = GlobalKey<FormState>();

  @override
  void dispose() {

   supabase.dispose();
    _email_controller.dispose();
    _passwodrd_controller.dispose();
    super.dispose();
  }
  @override
  void initState() {

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SingleChildScrollView(
          child: Form(
            key: _formKey,
            child: Padding(
              padding: EdgeInsets.all(20.0),

              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  Container(
                      alignment: Alignment.center,
                      child: Text("خوش آمدید",style: TextStyle(fontSize: 25.0,fontFamily: 'Dana'),)),
                  SizedBox(height: 30.0,),
                  TextInput(hint: 'ایمیل',controller: _email_controller,),
                  TextInput(hint: 'رمز عبور',controller: _passwodrd_controller,ispassword: true,),
                  SizedBox(height: 20.0,),
                  /*
                  login button
                   */
                  _signinLoading ? const Center(child: CircularProgressIndicator(),):
                  ElevatedButton(onPressed: ()async{
                    setState(() {
                      _signinLoading = true;
                    });
                    try{

                     await supabase.auth.signInWithPassword(email: _email_controller.text,password: _passwodrd_controller.text);
                    }catch(e){
                      ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("error: ${e.toString()}"),
                      backgroundColor: Colors.redAccent,));
                      setState(() {
                        _signinLoading = false;
                      });
                    }
                  },

                      child: Text("Login")
                  ),
                  /*
                  sing up button
                   */
                  _signUpLoading ? const Center(child: CircularProgressIndicator(),):
                  OutlinedButton(onPressed: ()async{
                    setState(() {
                      _signUpLoading = true;
                    });
                    try{

                     await supabase.auth.signUp(email: _email_controller.text,password: _passwodrd_controller.text);
                      ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("لینک تایید ارسال شد"),
                        backgroundColor: Colors.greenAccent,));
                     setState(() {
                       _signUpLoading = false;
                     });
                    }catch(e){
                      ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("error: ${e.toString()}"),
                        backgroundColor: Colors.redAccent,));
                      setState(() {
                        _signUpLoading = false;
                      });
                    }
                  },

                      child: Text("Sign up")
                  )
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }


}

در کدهای بالا ابتدا یک نمونه از کلاس Supabase ایجاد کردیم.

سپس در ادامه با استفاده supabase.auth.signUp اقدام به ایجاد اکانت جدید میکنیم.

همچنین برای ورود به حساب کاربر نیز از دستور supabase.auth.signInWithPassword استفاده میکنیم.

بعد از اینکه کاربر با موفقیت وارد حساب کاربری خود شد به صفحه AdsList منتقل میشود.

در این صفحه لیست تمام آگهی های ثبت شده را نمایش خواهیم داد.

کدهای این صفحه به شکل زیر میباشد:

class AdsList extends StatefulWidget {
  const AdsList({Key? key}) : super(key: key);

  @override
  State<AdsList> createState() => _AdsListState();
}

class _AdsListState extends State<AdsList> {


  final SupabaseClient supabase = Supabase.instance.client;
  late Stream<List<Map<String,dynamic>>> _readStream;

  @override
  void initState() {
    // TODO: implement initState
    _readStream = supabase.from('ads').stream(primaryKey: ['id']);
    _readStream.listen((event) { });

    super.initState();
  }

  Future<dynamic> getData() async{
    final Future<dynamic> _future = supabase
        .from('ads')
        .select()
        .order('id',ascending: true);
    return _future;
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.white,
          centerTitle: true,
        title: Text('آگهی ها',style: TextStyle(fontFamily: 'Dana',color: Colors.black),),
        actions: [
          IconButton(onPressed: (){
            Navigator.push(context, MaterialPageRoute(builder: (context) => SendAds(),));
          },
              icon: Icon(Icons.add,color: Colors.black,)
          )
        ],
      ),
      body: Container(
        color: Color(0xffafafa),
        child: FutureBuilder(
          future: getData(),
          builder: (context, snapshot) {

            if(snapshot.hasData){
              Iterable _iterable = snapshot.data;
              var _list = _iterable.map((e) => Product.fromJson(e)).toList();
              print(_iterable.toString());
              return ListView.builder(
                itemCount: _iterable.length,
                  itemBuilder: (context, index) {
                    return InkWell(
                      onTap: (){
                        Navigator.push(context, MaterialPageRoute(builder: (context) => AdsDetail(_list[index]),));

                      },
                      child: AdsItem(_list[index]),
                    );
                  },
              );
            }else if(snapshot.hasError){
              return Center(child: Text("error: ${snapshot.error}"),);
            }else{
              return Center(child: Text("Loading...."),);
            }
          },
        ),
      ),
    );
  }
}

در این صفحه هم ابتدا یک نمونه از کلاس Supabase ایجاد میکنیم.

برای دریافت اطلاعات از پایگاه داده به دو روش کلی میتوانیم این کار را انجام دهیم.

یک روش استفاده از قابلیت استریم ها در فلاتر میباشد. بدین صورت هر زمان تغییراتی روی اطلاعات دیتابیس انجام شود به شکل بلادرنگ نیز این تغییرات را در بخش فلاتر خواهیم داشت.

روش دوم دریافت اطلاعات به شکل عادی می باشد که ارتباط با سرور بصورت یک طرفه برقرار میباشد.

داخل متد getData اطلاعات را از جدولی که میخواهیم دریافت میکنیم.

در قسمت from نام جدول مورد نظر را قرار میدهیم که اینجا ads نام دارد.

در ادامه اطلاعات دریافت شده را در داخل یک لیست ویو نمایش میدهیم.

در این کدها ویجتی به نام AdsItem وجود دارد که مسئول نمایش اطلاعات هر آیتم از لیست میباشد.

کدهای این کلاس به شکل زیر میباشد:

class AdsItem extends StatelessWidget {

  Product pr;


  AdsItem(this.pr);

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.symmetric(horizontal: 12.0,vertical: 5.0),
      elevation: 1.0,
      color: Colors.white,
      child: Container(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              height: 130.0,
              margin: EdgeInsets.only(bottom: 6.0,top: 4.0,right: 5.0,left: 5.0),
              decoration: BoxDecoration(
                borderRadius: BorderRadius.all(Radius.circular(9.0)),
                image: DecorationImage(
                  fit: BoxFit.fill,
                  image: NetworkImage(pr.image!)
                )
              ),
            ),
            Container(
              alignment: Alignment.topRight,
              margin: EdgeInsets.symmetric(horizontal: 10.0,vertical: 6.0),
              child: Text(pr.title!,style: TextStyle(fontFamily: 'Dana',fontSize: 19.0,color: Colors.black),),
              
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                Container(
                  margin: EdgeInsets.symmetric(horizontal: 5.0),
                  child: Text(pr.city!,style: TextStyle(fontFamily: 'Dana',fontSize: 16.0,color: Colors.black),),
                ),
                Icon(Icons.location_on_outlined,color: Colors.black,)
              ],
            )
          ],
        ),
      ),
    );
  }
}

صفحه مهم بعدی که کار طراحی آن را انجام میدهیم صفحه SendAds میباشد.

در این صفحه کاربر با انتخاب یک تصویر و وارد کردن اطلاعات مختلف یک آگهی جدیدی را در سیستم ثبت میکند.

کدهای این صفحه به شکل زیر میباشد:

class SendAds extends StatefulWidget {
  const SendAds({Key? key}) : super(key: key);

  @override
  State<SendAds> createState() => _SendAdsState();
}

class _SendAdsState extends State<SendAds> {

  bool _isUPloading = false;
  final SupabaseClient supabase = Supabase.instance.client;
  TextEditingController title_controller = TextEditingController();
  TextEditingController desc_controller = TextEditingController();
  TextEditingController price_controller = TextEditingController();

  List<String> cat_list = ["خانه","ماشین","موبایل"];
  List<String> city_list = ["تهران","رشت","قشم"];

  String city_value = "تهران";
  String cat_value = "خانه";


  Future<String?> uploadFile() async{


    var pickedfile = await FilePicker.platform.pickFiles(allowMultiple: false,type: FileType.image);
    if(pickedfile!=null){
      setState(() {
        _isUPloading = true;
      });
      try{
        var file_path = "https://yuimzptrjihrqqzuibkh.supabase.co/storage/v1/object/public/";
        File file = File(pickedfile.files.first.path!);
        String filename = pickedfile.files.first.name;
        String uploadfile = await supabase.storage.from('user-images').upload("${supabase.auth.currentUser!.id}/$filename", file);
       String file_url = await supabase.storage.from('user-images').getPublicUrl("${supabase.auth.currentUser!.id}/$filename");
        print(uploadfile);
      print(file_url);
      return file_url;
      }catch(e){
          print("Error: ${e.toString()}");
          setState(() {
            _isUPloading = false;
          });
          ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("error: ${e.toString()}"),
            backgroundColor: Colors.redAccent,));
      }


    }
  }


  Future insertData() async {
    String? img_url  = await uploadFile();


    try{
      String userId = supabase.auth.currentUser!.id;
      await supabase.from('ads').insert({
        'userid': userId,
        'title': title_controller.text,
        'desc': desc_controller.text,
        'city': city_value,
        'category': city_value,
        "img":img_url,
        "price": int.parse(price_controller.text)
      });
      ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("اطلاعات ثبت شد"),
        backgroundColor: Colors.greenAccent,));
      setState(() {
        _isUPloading = false;
      });
    }catch(e){
      print("Error: ${e.toString()}");
      setState(() {
        _isUPloading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("error: ${e.toString()}"),
        backgroundColor: Colors.redAccent,));
    }
  }

  @override
  void initState() {
    // TODO: implement initState
    cat_value = cat_list[0];
    city_value = city_list[0];
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: _isUPloading? const CircularProgressIndicator(): FloatingActionButton(
        child: Icon(Icons.access_alarm_outlined),onPressed: (){},),
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: Text("ثبت آگهی",style: TextStyle(fontFamily: 'Dana',color: Colors.black),),
      ),
      body: Container(
        color: Color(0xffafafa),
        child: Column(
          children: [
            TextInput(controller: title_controller,hint: 'عنوان',),
            TextInput(controller: desc_controller,hint: 'توضیحات',),
            TextInput(controller: price_controller,hint: 'قیمت',),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [

                DropdownButton<String>(
                  onChanged: (String? newValue){
                    setState(() {
                      city_value = newValue!;
                    });
                  },
                  value: city_value,
                  items: city_list.map<DropdownMenuItem<String>>((e) => DropdownMenuItem<String>(
                    value: e,
                    child: Text(e.toString(),style: TextStyle(fontFamily: 'Dana'),),
                  )).toList()
                ),
                DropdownButton<String>(
                    onChanged: (String? newValue){
                      setState(() {
                        cat_value = newValue!;
                      });
                    },
                    value: cat_value,
                    items: cat_list.map<DropdownMenuItem<String>>((e) => DropdownMenuItem<String>(
                      value: e,
                      child: Text(e.toString(),style: TextStyle(fontFamily: 'Dana'),),
                    )).toList()
                )
              ],
            ),
            Container(
              margin: EdgeInsets.symmetric(vertical: 15.0),
              child: ElevatedButton(
                child: Text('انتخاب تصویر',style: TextStyle(fontFamily: 'Dana')),
                onPressed: () async{
                  insertData();
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}

در این صفحه سه ورودی متن به همراه کنترلرهای آنها در اختیار داریم.

همچنین برای انتخاب شهر و دسته بندی آگهی از ویجت فلاتر DropDownMenu استفاده میکنیم.

متد uploadFile وظیفه آپلود تصویر انتخاب شده را برعهده دارد.

آموزش دیتابیس Realm در فلاتر

با دستور supabase.storage.from(‘user-images’).upload فایل مورد نظر در باکتی به نام user-images ذخیره خواهد شد.

در صورتی که این کار با موفقیت انجام شود آدرس لینک تصویر توسط این متد بازگردانده میشود.

در متد insertData ردیف جدیدی از اطلاعات را داخل دیتابیس ذخیره میکنیم.

این کار با استفاده از دستور supabase.from(‘ads’).insert انجام میشود.

برای آموزش بهتر تمام موارد پیاده سازی شده در این پروژه در قالب دو ویدیو آموزشی برای شما فراهم شده است که میتوانید از آنها استفاده کنید.

5 پاسخ به “آموزش کار با Supabse در فلاتر جایگزین فایربیس”

  1. محمد گفت:

    سلام مگه supabase مثل فایربیس تحریم نیست ؟

  2. احمد جودکی گفت:

    سوپابیس برای اهراز هویت امنیت و سرعتش خوبه؟!

    • Hesam گفت:

      سلام, بله مشکلی نداره

      • احمد جودکی گفت:

        تشکر فقط من الان با توجه به اموزش بالا جلو رفتم در قسمت send_ads بعد از ارسال عکس از برنامه میره بیرون متوجه نمی شم خطاش کجاس

        • احمد جودکی گفت:

          مشکل حل شد پکیج file_picker ورژن آخرش رو شبیه ساز من کار نمی کرد همون ورژنی که تو ویدیو گذاشته شده بود رو گذاشتم درست شد البته روی گوشی هم عکس رو نمی فرستاد که polici های مربوط به ‘user_images’ که ساخته بودید رو گذاشتم درست شد درکد ها هم داخل send_ads قسمت insertData در معرفی متغیر ها قسمت ‘city_value’و ‘cat_value’ هردو رو ‘city_value’گذاشته بودید ممنون از ویدیو آموزنده تون

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Hesam
21 مه 2023
آموزش فارسی فلاتر
آموزش فارسی flutter