ফ্লাটার অ্যাপ থেকে গুগল ড্রাইভে ডেটা ব্যাকআপ এবং রিস্টোর

ফ্লাটার অ্যাপ থেকে ইউজারের গুগল ড্রাইভে ইউজার ডেটা ব্যাকআপ রাখার অপশন দিতে পারি। হোয়াটসঅ্যাপে যেমন ব্যাকআপ রাখা যায়, তেমন আরকি।

এর জন্য প্রথমে গুগল ক্লাউড কনসোলে গিয়ে একটা অ্যাপ তৈরি করে নিতে হবে। এর জন্য ফায়ারবেজ লাগবে না। প্রজেক্টের যে কোন নাম দিতে পারব। কয়েকটা স্টেপ আমাদের ফলো করতে হবে।

১) গুগল ড্রাইভ এপিআই এনাবল করা

এরপর এই প্রজেক্টে গুগল ড্রাইভ এপিআই এনাবল করতে হবে। তার জন্য সাইডবার থেকে APIs & Services > Library তে ক্লিক করতে হবে।

এখান থেকে গুগল ড্রাইভ এপিআইতে ক্লিক করে এনাবল করে নেওয়া যাবে।

২) Google Auth Platform সেটআপ করা

আগের মত সাইডবার থেকে APIs & Services > OAuth consent screen এ ক্লিক করব। এখানে গিয়ে Google Auth Platform সেটআপ করে নিতে হবে।

এখানে অ্যাপের নাম সহ অন্যান্য তথ্য ফিল করে ।

৩)  OAuth clients তৈরি

এই ক্লায়েন্ট হচ্ছে আমাদের অ্যাপ কোন কোন প্লাটফর্মে ব্যবহার করব, তা। বামপাশ থেকে Clients এ ক্লিক করব। এরপর উপড়ে Create Clients এ ক্লিক করব।

ফ্লাটার অ্যাপ অ্যান্ড্রয়েড এবং আইওএস দুই প্লাটফর্মের জন্য ডেভেলপ করলে একাধিক ক্লায়েন্ট তৈরি করে নিতে হবে। এখানে উদাহরণ হিসেবে অ্যান্ড্রয়েড ক্লায়েন্ট তৈরি করব।

এখানে অ্যাপ যদি পাবলিশ করা থাকে, তাহলে অ্যাপ ভেরিফাই করা যাবে। এবং অ্যাপ পাবলিশ করতে যে কী ব্যবহার করেছি, ঐ কী দিয়ে একটা SHA-1 ফিঙ্গারপ্রিন্ট জেনারেট করে নিতে হবে। পাবলিশ না করা থাকলে আমরা ডিভাগ কী ব্যবহার করে SHA-1 ফিঙ্গারপ্রিন্ট জেনারেট করে নিতে পারব।

keytool -list -v \
  -alias androiddebugkey \
  -keystore ~/.android/debug.keystore \
  -storepass android \
  -keypass android

ডিফল্ট ভাবে এন্ড্রয়েডের ডিভাগ কী লোকেশন ~/.android/debug.keystore এ থাকে। ঐ কীস্টোর এর পাস এবং কী পাস হচ্ছে android। তাই উপরের কমান্ড রান করলে আমাদের SHA-1 ফিঙ্গারপ্রিন্ট জেনারেট করে দিবে। যা উপরে OAuth clients তৈরিতে ব্যবহার করব।

অ্যাপ রিলিজ করলে আরেকটা OAuth clients তৈরি করে নিব। কারণ এখন প্রতিটা ক্লায়েন্টের জন্য একটাই SHA-1 ফিঙ্গারপ্রিন্ট ব্যবহার করা যায়। ফায়ারবেজে যদিও একই প্রজেক্টে একই সাথে ডিভাগ এবং রিলিজ সহ একাধিক SHA-1 ফিঙ্গারপ্রিন্ট যোগ করা যায়।

রিলিজের ক্ষেত্রে নিজেদের কীস্টোর এবং এর পাসওয়ার্ড ব্যবহার করতে হবে। তখন কমান্ড হবে এমনঃ

keytool -list -v \
  -alias your-key-alias \
  -keystore path/to/your/keystore.jks

যদিও অ্যাপ আমরা এখনো তৈরি করিনি। অ্যাপ তৈরি পরেও SHA-1 ফিঙ্গারপ্রিন্ট জেনারেট করে এখানে আপডেট করা যাবে।

৪) টেস্ট অডিয়েন্স যোগ করা

অ্যাপ পাবলিশ না করা থাকলে টেস্ট ইউজার যোগ করতে হবে। তা করা যাবে Audience থেকে।

নিচের দিকে টেস্ট ইউজার যোগ করার অপশন রয়েছে। যে ইমেইল বা ইউজার একাউন্ট ব্যবহার করে ড্রাইভে ব্যাকআপ টেস্ট করব, সে ইমেইল এখানে যোগ করতে হবে। এই তো।

উপরের স্টেপ গুলো হয়তো ভবিষ্যতে অন্যরকম থাকতে পারে। যেহেতু গুগল বার বার তাদের সার্ভিস এবং UI পরিবর্তন করে। তাই যদি কোন স্টেপ না মিলে, তাহলে একটু আপডেটেড ডকুমেন্ট দেখে নিতে হবে।

ফ্লাটার অ্যাপ তৈরি

এবার একটা অ্যাপ তৈরি করে নিব। প্রজেক্টে নিচের ডিপেন্ডেন্সি গুলো লাগবেঃ

  google_sign_in: ^6.3.0
  googleapis: ^14.0.0
  googleapis_auth: ^2.0.0
  http: ^1.4.0
  path_provider: ^2.1.5
  http_parser: ^4.1.2


অ্যাপে কয়েকটা স্টেপ রয়েছেঃ

গুগল সাইন-ইনঃ

final GoogleSignIn _googleSignIn = GoogleSignIn(
  scopes: [
    'email',
    'https://www.googleapis.com/auth/drive.file',
  ],
);

অথেনটিকেশন এবং অথ হেডারঃ

Future<Map<String, String>> getAuthHeaders() async {
  final account = await _googleSignIn.signIn();
  final auth = await account!.authentication;
  return {
    'Authorization': 'Bearer ${auth.accessToken}',
    'Content-Type': 'application/json',
  };
}

গুগল ড্রাইভে ফাইল আপলোডঃ

Future&lt;void> uploadFileToDrive(String content) async {
  final headers = await getAuthHeaders();

  final metadata = {
    'name': 'backup.json',
    'mimeType': 'application/json',
  };

  final request = http.MultipartRequest(
    'POST',
    Uri.parse('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'),
  );

  request.headers.addAll({'Authorization': headers['Authorization']!});

  request.files.add(http.MultipartFile.fromString(
    'metadata',
    jsonEncode(metadata),
    contentType: MediaType('application', 'json'),
  ));

  request.files.add(http.MultipartFile.fromString(
    'file',
    content,
    contentType: MediaType('application', 'json'),
  ));

  final response = await request.send();

  if (response.statusCode == 200) {
    print('✅ File uploaded successfully');
  } else {
    final body = await response.stream.bytesToString();
    print('❌ Upload failed: ${response.statusCode}');
    print('Response body: $body');
  }
}

এখানে আমরা সিম্পল একটা ফাইল আপলোড করব।

মিনিমাল UI:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Google Drive Backup")),
      body: Center(
        child: ElevatedButton(
          child: const Text("Backup Data"),
          onPressed: () async {
            await uploadFileToDrive(json.encode({'backup': 'Hello from Flutter!'}));
          },
        ),
      ),
    );
  }
}

সব গুলো কোড একসাথেঃ

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http_parser/http_parser.dart';


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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const DriveBackupApp(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Google Drive Backup")),
      body: Center(
        child: ElevatedButton(
          child: const Text("Backup Data"),
          onPressed: () async {
            await uploadFileToDrive(json.encode({'backup': 'Hello from Flutter!'}));
          },
        ),
      ),
    );
  }
}



Future&lt;void> uploadFileToDrive(String content) async {
  final headers = await getAuthHeaders();

  final metadata = {
    'name': 'backup.json',
    'mimeType': 'application/json',
  };

  final request = http.MultipartRequest(
    'POST',
    Uri.parse('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'),
  );

  request.headers.addAll({'Authorization': headers['Authorization']!});

  request.files.add(http.MultipartFile.fromString(
    'metadata',
    jsonEncode(metadata),
    contentType: MediaType('application', 'json'),
  ));

  request.files.add(http.MultipartFile.fromString(
    'file',
    content,
    contentType: MediaType('application', 'json'),
  ));

  final response = await request.send();

  if (response.statusCode == 200) {
    print('✅ File uploaded successfully');
  } else {
    final body = await response.stream.bytesToString();
    print('❌ Upload failed: ${response.statusCode}');
    print('Response body: $body');
  }
}


Future&lt;Map&lt;String, String>> getAuthHeaders() async {
  final account = await _googleSignIn.signIn();
  final auth = await account!.authentication;
  return {
    'Authorization': 'Bearer ${auth.accessToken}',
    'Content-Type': 'application/json',
  };
}

final GoogleSignIn _googleSignIn = GoogleSignIn(
  scopes: [
    'email',
    'https://www.googleapis.com/auth/drive.file',
  ],
);

অ্যাপ রান করলে যখন আমরা Backup Data তে ক্লিক করব, তখন প্রথমবার লগিন করতে হবে। এরপর ডেটা ব্যাকআপ হবে। সঠিক ভাবে সব স্টেপ ফলো করলে যে ইমেইল দিয়ে লগিন করব, ঐ ইউজারের গুগল ড্রাইভে গেলে আমরা backup.json নামে একটা ফাইল দেখতে পাবো।

ড্রাইভ থেকে ডেটা রিস্টোরঃ

Future<String?> downloadFileFromDrive(String fileId) async {
  final headers = await getAuthHeaders();

  final response = await http.get(
    Uri.parse('https://www.googleapis.com/drive/v3/files/$fileId?alt=media'),
    headers: headers,
  );

  if (response.statusCode == 200) {
    print("✅ File content: ${response.body}");
    return response.body;
  } else {
    print("❌ Failed to download: ${response.statusCode}");
    return null;
  }
}


Future<String?> getFileIdByName(String fileName) async {
  final headers = await getAuthHeaders();

  final response = await http.get(
    Uri.parse('https://www.googleapis.com/drive/v3/files?q=name="$fileName" and trashed=false'),
    headers: headers,
  );

  if (response.statusCode == 200) {
    final jsonMap = jsonDecode(response.body);
    final files = jsonMap['files'];
    if (files != null && files.isNotEmpty) {
      return files[0]['id'];
    }
  }
  return null;
}


void restoreBackup() async {
  final fileId = await getFileIdByName('backup.json');
  if (fileId != null) {
    final content = await downloadFileFromDrive(fileId);
    if (content != null) {
      final data = jsonDecode(content);
      print("Restored data: $data");
    }
  }
}

প্রথমে ফাইলের ID লাগবে। ফাইলের নাম পাস করলে ফাইলের আইডি পাবো। এরপর ঐ ফাইল ডাউনলোড করব ড্রাইভ থেকে। UI তে একটা বাটন যোগ করতে পারিঃ

ElevatedButton(
  child: const Text("Restore Data"),
  onPressed: () async {
    await restoreBackup();
  },
),

মিনিমাল ব্যাকআপ এবং রিস্টোরের কোড একসাথেঃ

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http_parser/http_parser.dart';

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

final GoogleSignIn _googleSignIn = GoogleSignIn(
  scopes: [
    'email',
    'https://www.googleapis.com/auth/drive.file',
  ],
);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Drive Backup App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const DriveBackupApp(),
    );
  }
}

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

  @override
  State<DriveBackupApp> createState() => _DriveBackupAppState();
}

class _DriveBackupAppState extends State<DriveBackupApp> {
  String restoredData = 'No data restored yet';

  Future<void> restoreBackup() async {
    final fileId = await getFileIdByName('backup.json');
    if (fileId != null) {
      final content = await downloadFileFromDrive(fileId);
      if (content != null) {
        final data = jsonDecode(content);
        setState(() {
          restoredData = data.toString();
        });
      } else {
        setState(() {
          restoredData = '❌ No content found';
        });
      }
    } else {
      setState(() {
        restoredData = '❌ File not found';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Google Drive Backup")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            ElevatedButton(
              child: const Text("Backup Data"),
              onPressed: () async {
                await uploadFileToDrive(json.encode({'backup': 'Hello from Flutter!'}));
              },
            ),
            ElevatedButton(
              child: const Text("Restore Data"),
              onPressed: () async {
                await restoreBackup();
              },
            ),
            const SizedBox(height: 20),
            const Text("Restored Data:", style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 10),
            Expanded(
              child: SingleChildScrollView(
                child: Text(restoredData),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// Upload file to Drive
Future<void> uploadFileToDrive(String content) async {
  final headers = await getAuthHeaders();

  final metadata = {
    'name': 'backup.json',
    'mimeType': 'application/json',
  };

  final request = http.MultipartRequest(
    'POST',
    Uri.parse('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'),
  );

  request.headers.addAll({'Authorization': headers['Authorization']!});

  request.files.add(http.MultipartFile.fromString(
    'metadata',
    jsonEncode(metadata),
    contentType: MediaType('application', 'json'),
  ));

  request.files.add(http.MultipartFile.fromString(
    'file',
    content,
    contentType: MediaType('application', 'json'),
  ));

  final response = await request.send();

  if (response.statusCode == 200) {
    print('✅ File uploaded successfully');
  } else {
    final body = await response.stream.bytesToString();
    print('❌ Upload failed: ${response.statusCode}');
    print('Response body: $body');
  }
}

// Get headers with access token
Future<Map<String, String>> getAuthHeaders() async {
  GoogleSignInAccount? account = _googleSignIn.currentUser;

  if (account == null) {
    account = await _googleSignIn.signInSilently();
    account ??= await _googleSignIn.signIn();
  }

  final auth = await account!.authentication;
  return {
    'Authorization': 'Bearer ${auth.accessToken}',
    'Content-Type': 'application/json',
  };
}

// Download file content from Drive
Future<String?> downloadFileFromDrive(String fileId) async {
  final headers = await getAuthHeaders();

  final response = await http.get(
    Uri.parse('https://www.googleapis.com/drive/v3/files/$fileId?alt=media'),
    headers: headers,
  );

  if (response.statusCode == 200) {
    print("✅ File content: ${response.body}");
    return response.body;
  } else {
    print("❌ Failed to download: ${response.statusCode}");
    return null;
  }
}

// Search file by name
Future<String?> getFileIdByName(String fileName) async {
  final headers = await getAuthHeaders();

  final response = await http.get(
    Uri.parse('https://www.googleapis.com/drive/v3/files?q=name="$fileName" and trashed=false'),
    headers: headers,
  );

  if (response.statusCode == 200) {
    final jsonMap = jsonDecode(response.body);
    final files = jsonMap['files'];
    if (files != null && files.isNotEmpty) {
      return files[0]['id'];
    }
  }
  return null;
}

আউডপুটঃ

Leave a Comment