parse-community / Parse-SDK-Flutter

The Dart/Flutter SDK for Parse Platform
https://parseplatform.org
Apache License 2.0
575 stars 190 forks source link

fix: Remove flutter version and add `example` and `example_ui` #852

Closed mbfakourii closed 1 year ago

mbfakourii commented 1 year ago

New Pull Request Checklist

Issue Description

In order to reduce conflicts, it was decided to remove the Flutter version, so the following tasks should be done

Closes: #850

Approach

n/a

TODOs before merging

parse-github-assistant[bot] commented 1 year ago

I will reformat the title to use the proper commit message syntax.

parse-github-assistant[bot] commented 1 year ago

Thanks for opening this pull request!

mbfakourii commented 1 year ago

@parse-community/flutter-sdk-review Are there other tasks?

Nidal-Bakir commented 1 year ago

If we will merge this we need first to publish a new version of the Flutter SDK to inform the package user about this transition.

TODOs for the README file in the Flutter SDK:

TODOs for the README file in the Dart SDK(new):

let me know if I missed anything.

Nidal-Bakir commented 1 year ago

Are there other tasks?

For now, I do not think of any other tasks.

mbfakourii commented 1 year ago

@parse-community/flutter-sdk-review Almost all tasks have been completed. please check it and not sure if CI works !

mbfakourii commented 1 year ago

@parse-community/flutter-sdk-review I made the suggested changes, is it possible to check again and is it ready for merge?

Nidal-Bakir commented 1 year ago

3 checks are reporting errors. we need to fix them before!

codecov[bot] commented 1 year ago

Codecov Report

Patch coverage: 75.00% and project coverage change: +0.55 :tada:

Comparison is base (94be044) 16.36% compared to head (2340211) 16.92%.

:exclamation: Current head 2340211 differs from pull request most recent head f3aab34. Consider uploading reports for the commit f3aab34 to get more accurate results

Additional details and impacted files ```diff @@ Coverage Diff @@ ## master #852 +/- ## ========================================== + Coverage 16.36% 16.92% +0.55% ========================================== Files 47 43 -4 Lines 2902 2653 -249 ========================================== - Hits 475 449 -26 + Misses 2427 2204 -223 ``` | [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) | Coverage Δ | | |---|---|---| | [lib/src/data/parse\_core\_data.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9kYXRhL3BhcnNlX2NvcmVfZGF0YS5kYXJ0) | `71.73% <ø> (ø)` | | | [lib/src/data/parse\_subclass\_handler.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9kYXRhL3BhcnNlX3N1YmNsYXNzX2hhbmRsZXIuZGFydA==) | `37.93% <ø> (ø)` | | | [lib/src/network/dio\_adapter\_io.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL2Rpb19hZGFwdGVyX2lvLmRhcnQ=) | `0.00% <ø> (ø)` | | | [lib/src/network/options.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL29wdGlvbnMuZGFydA==) | `0.00% <ø> (ø)` | | | [lib/src/network/parse\_client.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL3BhcnNlX2NsaWVudC5kYXJ0) | `20.00% <ø> (ø)` | | | [lib/src/network/parse\_dio\_client.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL3BhcnNlX2Rpb19jbGllbnQuZGFydA==) | `0.00% <ø> (ø)` | | | [lib/src/network/parse\_http\_client.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL3BhcnNlX2h0dHBfY2xpZW50LmRhcnQ=) | `5.12% <ø> (ø)` | | | [lib/src/network/parse\_live\_query.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL3BhcnNlX2xpdmVfcXVlcnkuZGFydA==) | `0.00% <ø> (ø)` | | | [lib/src/network/parse\_query.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL3BhcnNlX3F1ZXJ5LmRhcnQ=) | `22.31% <ø> (ø)` | | | [lib/src/network/parse\_websocket\_io.dart](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-bGliL3NyYy9uZXR3b3JrL3BhcnNlX3dlYnNvY2tldF9pby5kYXJ0) | `0.00% <ø> (ø)` | | | ... and [14 more](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) | | ... and [42 files with indirect coverage changes](https://codecov.io/gh/parse-community/Parse-SDK-Flutter/pull/852/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community)

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.

Nidal-Bakir commented 1 year ago

We still need a migration guide for the developer coming from the Flutter SDK

mbfakourii commented 1 year ago

@Nidal-Bakir Thanks ci fixed

mbfakourii commented 1 year ago

We still need a migration guide for the developer coming from the Flutter SDK

What should we explain in this guide ? The developer should only use Dart instead of the Flutter version !

Nidal-Bakir commented 1 year ago

If you look at the parse_server_sdk.dart in the Flutter package and the parse_server_sdk.dart in the Dart package. You will see the difference, the flutter package uses default values if the developer did not provide a value

here is a list of the parameters that should be required in the Dart SDK from now on...

kindly compare the three files:

and see the issues that will pop in the face of the developers because the defaults are not there anymore

Nidal-Bakir commented 1 year ago

One of the downsides of this transition is that the package user should provide arguments for parameters that were optional ones previously.

catalunha commented 1 year ago

I'm following this journey message by message. And the solution is fantastic. Because actually Parse Server is more Dart than Flutter. But @Nidal-Bakir last approach made me curious.

I use b4a's ParseServer on Dart and I don't need all these parameters. Because with the elimination of Flutter would I need it?

mbfakourii commented 1 year ago

If you look at the parse_server_sdk.dart in the Flutter package and the parse_server_sdk.dart in the Dart package. You will see the difference, the flutter package uses default values if the developer did not provide a value

here is a list of the parameters that should be required in the Dart SDK from now on...

  • appPackageName
  • appName
  • appVersion
  • locale
  • coreStore (CoreStoreSharedPrefsImp,CoreStoreMemoryImp,CoreStoreSembastImp)
  • connectivityProvider (For liveQuery)
  • fileDirectory

kindly compare the three files:

and see the issues that will pop in the face of the developers because the defaults are not there anymore

Do you think this description should be in a new file or in README.md?

mbfakourii commented 1 year ago

I'm following this journey message by message. And the solution is fantastic. Because actually Parse Server is more Dart than Flutter. But @Nidal-Bakir last approach made me curious.

I use b4a's ParseServer on Dart and I don't need all these parameters. Because with the elimination of Flutter would I need it?

These parameters are optional and do not need to be sent to the server, but an explanation can be given about coreStore and connectivityProvider and fileDirectory.

Nidal-Bakir commented 1 year ago

I use b4a's ParseServer on Dart and I don't need all these parameters. Because with the elimination of Flutter would I need it?

If you are using the Parse Dart SDK and the default configuration work with you then you are good to go no need to pass any additional argument.

But the Flutter framework users will have to pass arguments to these parameters . Because the Parse Dart SDK will have different behavior than the Parse Flutter SDK if they didn't pass arguments to these parameters.

For example, the Parse Flutter SDK will automatically provide a connectivityProvider to the SDK, but the Parse Dart SDK will not provide connectivityProvider and will keep it null.

The liveQuery feature expect a connectivityProvider so when the connection is cut off and then restored again later the liveQuery will try to reconnect to the server. if the user did not pass a connectivityProvider the liveQuery will NOT work https://github.com/parse-community/Parse-SDK-Flutter/blob/94be0442883d22056a1e898384abc3adf1495990/packages/dart/lib/src/network/parse_live_query.dart#L42-L50

Another example, the Parse Flutter SDK will provide the CoreStoreSharedPrefsImp as default storage but the Parse Dart SDK will not provide any storage option and will fallback to the CoreStoreMemoryImp

https://github.com/parse-community/Parse-SDK-Flutter/blob/94be0442883d22056a1e898384abc3adf1495990/packages/dart/lib/src/data/parse_core_data.dart#L43

Nidal-Bakir commented 1 year ago

One of the things we need to do is we need to make it crystal clear that the liveQuery feature will not work without a connectivityProvider argument

Nidal-Bakir commented 1 year ago

Do you think this description should be in a new file or in README.md?

We need a proper migration guide, steps, and code documentation (parameters and behaviors)

Parse_Dart_SDK is NOT one-drop replacement for Parse_Flutter_SDK

catalunha commented 1 year ago

Thanks for the detailed explanation @Nidal-Bakir I believe that some users of Parse_Flutter_SDK like me will not have this vision due to the facilities already presented by Parse_Flutter_SDK. I support your warnings that the README.md should contain a detailed step-by-step migration to Parse_Dart_SDK. Avoiding affecting users in the continuity of their projects. Once I have the beta version of this migration it will be a pleasure to test it on some of my projects to help them. Thanks to you and everyone else for this fantastic contribution.

Nidal-Bakir commented 1 year ago

@mtrezza Can I help him with adding code, documentation, and guides? It will be faster if we collaborate.

If yes, do I have a write permission to push commits to this PR?

mtrezza commented 1 year ago

do I have a write permission to push commits to this PR?

I don't assume that you have write permissions given your team membership, but you could just try to push a commit. If it fails, then @mbfakourii could give you access to his fork so you can push to his branch to collaborate.

We need a proper migration guide, steps, and code documentation (parameters and behaviors) Parse_Dart_SDK is NOT one-drop replacement for Parse_Flutter_SDK

In order to merge the 2 packages, the complexity to migrate should be manageable, even for a less proficient developer.

So this trickles down 1-2-3. The better the 1st and 2nd focus, the less migration guide is needed for us to write and for the developer to read.

Nidal-Bakir commented 1 year ago

To be honest the developer will write all the code that was previously provided by the Flutter SDK. which is:

in summary, we are asking the developers to code the Flutter SDK

And that is what we called boilerplate.

I hope we think again about this transition, even if the parse community, in general, moving toward one package/repo. sometimes it is better to have more than one package, so the SDK users do not need to do a lot of work to set up the SDK and use it.

No one loves to write a boilerplate

mbfakourii commented 1 year ago

@Nidal-Bakir Do you have push access now?

catalunha commented 1 year ago

I fully agree @Nidal-Bakir

Improvement for some would complicate for many.

Considering my level of knowledge and necessary work compared to what I already have to use, it would not be good news.

mbfakourii commented 1 year ago

I fully agree @Nidal-Bakir

Improvement for some would complicate for many.

Considering my level of knowledge and necessary work compared to what I already have to use, it would not be good news.

I think it can be solved with an explanation

Nidal-Bakir commented 1 year ago

Do you have push access now?

Yes, thanks

Nidal-Bakir commented 1 year ago

let us wait and see what @mtrezza will say. it's up to him to decide what should we do.

mbfakourii commented 1 year ago

@Nidal-Bakir

The coreStore (CoreStoreSharedPrefsImp,CoreStoreMemoryImp,CoreStoreSembastImp) problem has been solved in this file. The idea of using dynamics was to see if it is a good method or not.

and how to use

coreStore: await CoreStoreSharedPrefsImp.getInstance(
          sharedPreferences: (await SharedPreferences.getInstance())),

Any issue related to Flutter can be transferred outside

Nidal-Bakir commented 1 year ago

That's what the Flutter SDK was doing!! now the developers need to do it manually.

as @catalunha said:

Considering my level of knowledge and necessary work compared to what I already have to use, it would not be good news.

The package users already not liking this transition! because they need to write code that was hidden in the Flutter SDK.

do not forget all of this:

* init a database with a path using path_porvider package if they need to use Sembast database

* create a connectivityProvider class and instantiate it if they need to use liveQuery

* specifying the fileDirectory using path_provider package so the SDK can store the downloaded files

* specifying appName, appVersion, appPackageName the Flutter SDK was using PackageInfo package

in summary, we are asking the developers to code the Flutter SDK

Nidal-Bakir commented 1 year ago

The SDK before this transition was plug-and-play no extra thing you need to do. Just set your parse API keys and you're good to go.

But now they need to install the SDK set the parse API keys, set up the database storage, file storage, appResumedStreamController, connectivityProvider for the live query, and specify the appName, appVersion, and appPackageName.

Literally, no one will like this over the old way. which was basically a plug-and-play no extra configuration was required.

Take a look at the firebase SDK they built a CLI to set up the SDK. So the developers do not need to do anything.

mtrezza commented 1 year ago

set up the database storage, file storage, appResumedStreamController, connectivityProvider for the live query, and specify the appName, appVersion, and appPackageName.

Can you help me understand why we could not provide this in the new single SDK? And could you give a code example for these, to get an idea of what that actually means in terms of coding effort for the developer?

Nidal-Bakir commented 1 year ago

Sure, here is the full story...

Can you help me understand why we could not provide this in the new single SDK?

Those packages depend on Flutter in their dependency to make platform-specific calls. but the new SDK should be os-agnostic. because it's pure Dart and if we use these packages in the new SKD now we are not a pure Dart package and we have a platform-specific code, even though the SDK itself not making these calls.

If we need to add these packages in the SDK we need to migrate the other way around. we need to remove the dart SDK and migrate to the Flutter SDK. then we can use packages that make calls to platform-specific functions. because in this case, a (Flutter package) package uses should use this package in the Flutter context (in a Flutter project) and not in a Dart project.

Another unexpected thing that will face the developers if we add these packages to the Dart SDK is that the developers expect the package to be pure dart so they can use it in a CLI for example or any context outside the Flutter framework, but here is the plot twist the SDK will download the entire Flutter framework for this simple console application because our dependency graph contains Flutter Framework itself as a dependency of a dependency that we are using.

The following code is from the path_provider package:

dependencies:
  flutter:
    sdk: flutter

As you can see the path_provider package depends on the Flutter framework.

In the world of Flutter and Dart packages, we have two types:

  1. Dart package (pure Dart): the package contains only dart code no platform-specific calls, nor Flutter-related code. an example of these types of packages would be math, HTTP, Dio, or any other package that is contained in itself. i.e. can run without interaction with the host OS or the Flutter framework.
  2. Flutter packages which in turn have tow types:

    • Flutter Package: a package that depends on the Flutter framework to do its work. you can use any package that calls functions on the host OS like path_provider.
    • Flutter Plugin: it is a type of package that will call the underlying platform by itself like the path_provider. using platform channels for example.

    Package types

    Packages can contain more than one kind of content:

    Dart packages: General packages written in Dart, for example, the path package. Some of these might contain Flutter-specific functionality and thus have a dependency on the Flutter framework, restricting their use to Flutter only, for example, the fluro package.

    Plugin packages: A specialized Dart package that contains an API written in Dart code combined with one or more platform-specific implementations.

    Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, or Linux, or any combination thereof.

Because the new SDK is a pure dart with no platform-specific code which means the new SDK is os-agnostic. i.e. we do not request features from any platform.


And could you give a code example for these, to get an idea of what that actually means in terms of coding effort for the developer?

Sure, here is a two code snippet the first one before the transition and the second after the transition:

Before the transition:

import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse {
  Future<void> init() async {
    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      coreStore: await CoreStoreSembastImp.getInstance(),
    );
  }
}

After the transition:

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse with WidgetsBindingObserver {
  StreamController<void> appResumedStreamController = StreamController();

  void init() async {
    final PackageInfo packageInfo = await PackageInfo.fromPlatform();
    final appName = packageInfo.appName;
    final appVersion = packageInfo.version;
    final appPackageName = packageInfo.packageName;

    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      appName: appName,
      appPackageName: appPackageName,
      appVersion: appVersion,
      coreStore: await CoreStoreSembastImp.getInstance(await databaseFile()),
      fileDirectory: await getTempDirectory(),
      connectivityProvider: MyConnectivityProvider(),
      locale: local(),
      appResumedStream: appResumedStreamController.stream,
    );
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    appResumedStreamController.sink.add(null);
  }

  String local() {
    return (kIsWeb ? ui.window.locale.toString() : Platform.localeName);
  }

  Future<String> databaseFile() async {
    final databaseDir = await getDatabaseDirectory();

    return path.join(databaseDir, 'parse', 'parse.db');
  }

  Future<String> getDatabaseDirectory() async {
    if (kIsWeb) {
      return '';
    }

    if (defaultTargetPlatform == TargetPlatform.iOS) {
      return (await path_provider.getLibraryDirectory()).path;
    }

    return (await path_provider.getApplicationDocumentsDirectory()).path;
  }

  Future<String> getTempDirectory() async {
    return (await path_provider.getTemporaryDirectory()).path;
  }
}

class MyConnectivityProvider extends ParseConnectivityProvider {
  @override
  Future<ParseConnectivityResult> checkConnectivity() async {
    switch (await Connectivity().checkConnectivity()) {
      case ConnectivityResult.wifi:
        return ParseConnectivityResult.wifi;
      case ConnectivityResult.mobile:
        return ParseConnectivityResult.mobile;
      case ConnectivityResult.none:
        return ParseConnectivityResult.none;
      default:
        return ParseConnectivityResult.wifi;
    }
  }

  @override
  Stream<ParseConnectivityResult> get connectivityStream {
    return Connectivity().onConnectivityChanged.map((ConnectivityResult event) {
      switch (event) {
        case ConnectivityResult.wifi:
          return ParseConnectivityResult.wifi;
        case ConnectivityResult.mobile:
          return ParseConnectivityResult.mobile;
        default:
          return ParseConnectivityResult.none;
      }
    });
  }
}

How to avoid this?

We have two options:

mtrezza commented 1 year ago
Nidal-Bakir commented 1 year ago

The dart package seems more like a classic "core module" that is pretty useless for developers because they will always run it on some OS, so they always need to wrap it into platform specific code, right?

At the end of the day yes, will run on some OS, the idea behind this "core module" (Dart SDK) is the separation of concerns. so that any OS-related call will be handled outside the core module (Dart SDK).

Why would we want to merge into a single dart package instead of a single flutter package? What's the practical application of a pure dart package?

That was a mistake, we did not realize the amount of manual work the developers will do to set up the SDK.

In your example code, can't we build this platform specific code into the package, so that the package contains code for multiple platforms, similar to a multiarchitecture binary? If the SDK could detect the platforms type then it can do that automatically. If not, then the developer could set it manually. For example, our API mail adapter supports multiple email services providers out of the box, the developer just needs to set the correct one and the adapter will automatically encode the payload so that the specific service provider can understand it.

yes, we can do that. by migrating to the Flutter SDK

catalunha commented 1 year ago

Just sharing @mtrezza

What's the practical application of a pure dart package?

In two of my projects I open a pure Dart connection to do CRUDs and Querys in Parse Serve from b4a. Performing all the tests I need. Not in the TDD standard, but being able to access Parse Serve from b4a from a simple App in pure Dart. So when I go to the Flutter application I already have all the CRUDs and Querys ready and it's just developing the interface.

pastordee commented 1 year ago

Sure, here is the full story...

Can you help me understand why we could not provide this in the new single SDK?

Those packages depend on Flutter in their dependency to make platform-specific calls. but the new SDK should be os-agnostic. because it's pure Dart and if we use these packages in the new SKD now we are not a pure Dart package and we have a platform-specific code, even though the SDK itself not making these calls.

If we need to add these packages in the SDK we need to migrate the other way around. we need to remove the dart SDK and migrate to the Flutter SDK. then we can use packages that make calls to platform-specific functions. because in this case, a (Flutter package) package uses should use this package in the Flutter context (in a Flutter project) and not in a Dart project.

Another unexpected thing that will face the developers if we add these packages to the Dart SDK is that the developers expect the package to be pure dart so they can use it in a CLI for example or any context outside the Flutter framework, but here is the plot twist the SDK will download the entire Flutter framework for this simple console application because our dependency graph contains Flutter Framework itself as a dependency of a dependency that we are using.

The following code is from the path_provider package:

dependencies:
  flutter:
    sdk: flutter

As you can see the path_provider package depends on the Flutter framework.

In the world of Flutter and Dart packages, we have two types:

  1. Dart package (pure Dart): the package contains only dart code no platform-specific calls, nor Flutter-related code. an example of these types of packages would be math, HTTP, Dio, or any other package that is contained in itself. i.e. can run without interaction with the host OS or the Flutter framework.
  2. Flutter packages which in turn have tow types:

    • Flutter Package: a package that depends on the Flutter framework to do its work. you can use any package that calls functions on the host OS like path_provider.
    • Flutter Plugin: it is a type of package that will call the underlying platform by itself like the path_provider. using platform channels for example.

Package types

Packages can contain more than one kind of content: Dart packages: General packages written in Dart, for example, the path package. Some of these might contain Flutter-specific functionality and thus have a dependency on the Flutter framework, restricting their use to Flutter only, for example, the fluro package. Plugin packages: A specialized Dart package that contains an API written in Dart code combined with one or more platform-specific implementations.

Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, or Linux, or any combination thereof.

Because the new SDK is a pure dart with no platform-specific code which means the new SDK is os-agnostic. i.e. we do not request features from any platform.

And could you give a code example for these, to get an idea of what that actually means in terms of coding effort for the developer?

Sure, here is a two code snippet the first one before the transition and the second after the transition:

Before the transition:

import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse {
  Future<void> init() async {
    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      coreStore: await CoreStoreSembastImp.getInstance(),
    );
  }
}

After the transition:

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse with WidgetsBindingObserver {
  StreamController<void> appResumedStreamController = StreamController();

  void init() async {
    final PackageInfo packageInfo = await PackageInfo.fromPlatform();
    final appName = packageInfo.appName;
    final appVersion = packageInfo.version;
    final appPackageName = packageInfo.packageName;

    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      appName: appName,
      appPackageName: appPackageName,
      appVersion: appVersion,
      coreStore: await CoreStoreSembastImp.getInstance(await databaseFile()),
      fileDirectory: await getTempDirectory(),
      connectivityProvider: MyConnectivityProvider(),
      locale: local(),
      appResumedStream: appResumedStreamController.stream,
    );
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    appResumedStreamController.sink.add(null);
  }

  String local() {
    return (kIsWeb ? ui.window.locale.toString() : Platform.localeName);
  }

  Future<String> databaseFile() async {
    final databaseDir = await getDatabaseDirectory();

    return path.join(databaseDir, 'parse', 'parse.db');
  }

  Future<String> getDatabaseDirectory() async {
    if (kIsWeb) {
      return '';
    }

    if (defaultTargetPlatform == TargetPlatform.iOS) {
      return (await path_provider.getLibraryDirectory()).path;
    }

    return (await path_provider.getApplicationDocumentsDirectory()).path;
  }

  Future<String> getTempDirectory() async {
    return (await path_provider.getTemporaryDirectory()).path;
  }
}

class MyConnectivityProvider extends ParseConnectivityProvider {
  @override
  Future<ParseConnectivityResult> checkConnectivity() async {
    switch (await Connectivity().checkConnectivity()) {
      case ConnectivityResult.wifi:
        return ParseConnectivityResult.wifi;
      case ConnectivityResult.mobile:
        return ParseConnectivityResult.mobile;
      case ConnectivityResult.none:
        return ParseConnectivityResult.none;
      default:
        return ParseConnectivityResult.wifi;
    }
  }

  @override
  Stream<ParseConnectivityResult> get connectivityStream {
    return Connectivity().onConnectivityChanged.map((ConnectivityResult event) {
      switch (event) {
        case ConnectivityResult.wifi:
          return ParseConnectivityResult.wifi;
        case ConnectivityResult.mobile:
          return ParseConnectivityResult.mobile;
        default:
          return ParseConnectivityResult.none;
      }
    });
  }
}

How to avoid this?

We have two options:

  • If we still insist on one package/repo, then we need to migrate to the Flutter SDK and embed the Dart SDK in the Flutter SDK. But then the SDK can not run without the Flutter framework.
  • keep everything as is. two packages.

Hi guys, as I commented before this was the exact reason the package was split into two in the first please again I’ve been using it from day one and in the beginning it was together but because of platform-specific that’s why it was divided. I hope this helps.

pastordee commented 1 year ago

Sure, here is the full story...

Can you help me understand why we could not provide this in the new single SDK?

Those packages depend on Flutter in their dependency to make platform-specific calls. but the new SDK should be os-agnostic. because it's pure Dart and if we use these packages in the new SKD now we are not a pure Dart package and we have a platform-specific code, even though the SDK itself not making these calls.

If we need to add these packages in the SDK we need to migrate the other way around. we need to remove the dart SDK and migrate to the Flutter SDK. then we can use packages that make calls to platform-specific functions. because in this case, a (Flutter package) package uses should use this package in the Flutter context (in a Flutter project) and not in a Dart project.

Another unexpected thing that will face the developers if we add these packages to the Dart SDK is that the developers expect the package to be pure dart so they can use it in a CLI for example or any context outside the Flutter framework, but here is the plot twist the SDK will download the entire Flutter framework for this simple console application because our dependency graph contains Flutter Framework itself as a dependency of a dependency that we are using.

The following code is from the path_provider package:

dependencies:
  flutter:
    sdk: flutter

As you can see the path_provider package depends on the Flutter framework.

In the world of Flutter and Dart packages, we have two types:

  1. Dart package (pure Dart): the package contains only dart code no platform-specific calls, nor Flutter-related code. an example of these types of packages would be math, HTTP, Dio, or any other package that is contained in itself. i.e. can run without interaction with the host OS or the Flutter framework.
  2. Flutter packages which in turn have tow types:

    • Flutter Package: a package that depends on the Flutter framework to do its work. you can use any package that calls functions on the host OS like path_provider.
    • Flutter Plugin: it is a type of package that will call the underlying platform by itself like the path_provider. using platform channels for example.

Package types

Packages can contain more than one kind of content: Dart packages: General packages written in Dart, for example, the path package. Some of these might contain Flutter-specific functionality and thus have a dependency on the Flutter framework, restricting their use to Flutter only, for example, the fluro package. Plugin packages: A specialized Dart package that contains an API written in Dart code combined with one or more platform-specific implementations.

Plugin packages can be written for Android (using Kotlin or Java), iOS (using Swift or Objective-C), web, macOS, Windows, or Linux, or any combination thereof.

Because the new SDK is a pure dart with no platform-specific code which means the new SDK is os-agnostic. i.e. we do not request features from any platform.

And could you give a code example for these, to get an idea of what that actually means in terms of coding effort for the developer?

Sure, here is a two code snippet the first one before the transition and the second after the transition:

Before the transition:

import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse {
  Future<void> init() async {
    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      coreStore: await CoreStoreSembastImp.getInstance(),
    );
  }
}

After the transition:

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse with WidgetsBindingObserver {
  StreamController<void> appResumedStreamController = StreamController();

  void init() async {
    final PackageInfo packageInfo = await PackageInfo.fromPlatform();
    final appName = packageInfo.appName;
    final appVersion = packageInfo.version;
    final appPackageName = packageInfo.packageName;

    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      appName: appName,
      appPackageName: appPackageName,
      appVersion: appVersion,
      coreStore: await CoreStoreSembastImp.getInstance(await databaseFile()),
      fileDirectory: await getTempDirectory(),
      connectivityProvider: MyConnectivityProvider(),
      locale: local(),
      appResumedStream: appResumedStreamController.stream,
    );
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    appResumedStreamController.sink.add(null);
  }

  String local() {
    return (kIsWeb ? ui.window.locale.toString() : Platform.localeName);
  }

  Future<String> databaseFile() async {
    final databaseDir = await getDatabaseDirectory();

    return path.join(databaseDir, 'parse', 'parse.db');
  }

  Future<String> getDatabaseDirectory() async {
    if (kIsWeb) {
      return '';
    }

    if (defaultTargetPlatform == TargetPlatform.iOS) {
      return (await path_provider.getLibraryDirectory()).path;
    }

    return (await path_provider.getApplicationDocumentsDirectory()).path;
  }

  Future<String> getTempDirectory() async {
    return (await path_provider.getTemporaryDirectory()).path;
  }
}

class MyConnectivityProvider extends ParseConnectivityProvider {
  @override
  Future<ParseConnectivityResult> checkConnectivity() async {
    switch (await Connectivity().checkConnectivity()) {
      case ConnectivityResult.wifi:
        return ParseConnectivityResult.wifi;
      case ConnectivityResult.mobile:
        return ParseConnectivityResult.mobile;
      case ConnectivityResult.none:
        return ParseConnectivityResult.none;
      default:
        return ParseConnectivityResult.wifi;
    }
  }

  @override
  Stream<ParseConnectivityResult> get connectivityStream {
    return Connectivity().onConnectivityChanged.map((ConnectivityResult event) {
      switch (event) {
        case ConnectivityResult.wifi:
          return ParseConnectivityResult.wifi;
        case ConnectivityResult.mobile:
          return ParseConnectivityResult.mobile;
        default:
          return ParseConnectivityResult.none;
      }
    });
  }
}

How to avoid this?

We have two options:

  • If we still insist on one package/repo, then we need to migrate to the Flutter SDK and embed the Dart SDK in the Flutter SDK. But then the SDK can not run without the Flutter framework.
  • keep everything as is. two packages.

Sorry guys, I think we should keep the two packages. My reason for that is the dart version is the core and the flatter is built on top of that and anyone can build another package on top of it say a sign up package on top of the dart and people will have the choice to use it or not. Again anyone using the dart version do not need the flatter package , but if you use the flutter package, you will still need to import the data version into it. So I think we should leave the two packages and just allow people to build packages on top of the Dart package That’s my view.

Nidal-Bakir commented 1 year ago

@mtrezza We have wasted a lot of time on this. We need to make up our minds. There are a lot of open issues and feature requests besides improving the SDK, adding some documentation in the code, adding any missing features, and so on ...

Currently, the options are the next:

  1. drop the Flutter SDK and keep the Dart SDK (this PR)
  2. mege the Dart SDK in Fluttter SDK
  3. keep everything as it is. (the best option)
  4. defer this to another time
pastordee commented 1 year ago

@mtrezza We have wasted a lot of time on this. We need to make up our minds. There are a lot of open issues and feature requests besides improving the SDK, adding some documentation in the code, adding any missing features, and so on ...

Currently, the options are the next:

  1. drop the Flutter SDK and keep the Dart SDK (this PR)
  2. mege the Dart SDK in Fluttter SDK
  3. keep everything as it is. (the best option)
  4. defer this to another time

Option 3 sounds good for now and let’s take time and really think about this properly.

mbfakourii commented 1 year ago

In my opinion, we should solve the problem with some explanations, I think that the problem will be solved by explaining the data and the developers can adapt.

mbfakourii commented 1 year ago

We spent a lot of time on this PR, I don't think it's right to skip it

pastordee commented 1 year ago

We spent a lot of time on this PR, I don't think it's right to skip it

I don’t think it’s been skip is just that we need to take time and really think about it but in the meantime we should continue to update the flutter package .

mtrezza commented 1 year ago

Thanks everyone for their input in this discussion. The time is well spent if we come to the right conclusion.

My previous point is still unclear to me:

Why would we want to merge into a single dart package instead of a single flutter package? What's the practical application of a pure dart package?

And what's the practical downside of offering a flutter package instead of a dart package? From @catalunha's explaination it sounds like a developer would always want the flutter package, because they will always run it on some OS and may need platform specific code.

Whatever we have on other open bug fixes and features we can go ahead and merge in the meantime.

Nidal-Bakir commented 1 year ago

I have seen a lot of developers code the domain/business logic in the dart console application, so it's easier to test and run. no need for a device to run the code. And then take the results code and put it in a Flutter application and then implement the UI with the state management. Offering a Dart SDK enable them to do that. Because it is not dependent on the Flutter framework.

Another use case for the Dart SDK is if any developer needs to build a CLI application.

Offering a Flutter SDK only will prevent the use cases I mention above.

mtrezza commented 1 year ago

Got it, thanks. And can't we include the platform specific code that is mentioned in "After the transition" in the dart package to make it a "universal package"? The code contains flutter imports - could they be imported only optionally somehow?

Nidal-Bakir commented 1 year ago

And can't we include the platform specific code that is mentioned in https://github.com/parse-community/Parse-SDK-Flutter/pull/852#issuecomment-1473357297 in the dart package to make it a "universal package"?

Because any platform specific code requires the Flutter framework to be present. any platform specific call is done through the Flutter framework

The code contains flutter imports - could they be imported only optionally somehow?

These imports are declared outside the SDK. i.e. the developer is writing this.

the developer will write this.

After the transition:

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart' as path_provider;

var keyParseApplicationId = '';
var keyParseServerUrl = '';
var keyParseClientKey = '';
var liveQueryUrl = '';

class InitParse with WidgetsBindingObserver {
  StreamController<void> appResumedStreamController = StreamController();

  void init() async {
    final PackageInfo packageInfo = await PackageInfo.fromPlatform();
    final appName = packageInfo.appName;
    final appVersion = packageInfo.version;
    final appPackageName = packageInfo.packageName;

    await Parse().initialize(
      keyParseApplicationId,
      keyParseServerUrl,
      clientKey: keyParseClientKey,
      liveQueryUrl: liveQueryUrl,
      appName: appName,
      appPackageName: appPackageName,
      appVersion: appVersion,
      coreStore: await CoreStoreSembastImp.getInstance(await databaseFile()),
      fileDirectory: await getTempDirectory(),
      connectivityProvider: MyConnectivityProvider(),
      locale: local(),
      appResumedStream: appResumedStreamController.stream,
    );
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    appResumedStreamController.sink.add(null);
  }

  String local() {
    return (kIsWeb ? ui.window.locale.toString() : Platform.localeName);
  }

  Future<String> databaseFile() async {
    final databaseDir = await getDatabaseDirectory();

    return path.join(databaseDir, 'parse', 'parse.db');
  }

  Future<String> getDatabaseDirectory() async {
    if (kIsWeb) {
      return '';
    }

    if (defaultTargetPlatform == TargetPlatform.iOS) {
      return (await path_provider.getLibraryDirectory()).path;
    }

    return (await path_provider.getApplicationDocumentsDirectory()).path;
  }

  Future<String> getTempDirectory() async {
    return (await path_provider.getTemporaryDirectory()).path;
  }
}

class MyConnectivityProvider extends ParseConnectivityProvider {
  @override
  Future<ParseConnectivityResult> checkConnectivity() async {
    switch (await Connectivity().checkConnectivity()) {
      case ConnectivityResult.wifi:
        return ParseConnectivityResult.wifi;
      case ConnectivityResult.mobile:
        return ParseConnectivityResult.mobile;
      case ConnectivityResult.none:
        return ParseConnectivityResult.none;
      default:
        return ParseConnectivityResult.wifi;
    }
  }

  @override
  Stream<ParseConnectivityResult> get connectivityStream {
    return Connectivity().onConnectivityChanged.map((ConnectivityResult event) {
      switch (event) {
        case ConnectivityResult.wifi:
          return ParseConnectivityResult.wifi;
        case ConnectivityResult.mobile:
          return ParseConnectivityResult.mobile;
        default:
          return ParseConnectivityResult.none;
      }
    });
  }
}
Nidal-Bakir commented 1 year ago

Another convention in the Flutter/Dart packages world is that if any package on the pub.dev has the "Dart" in its name that means it does not need flutter to run.

And the same thing goes for any package that has the "Flutter" name in it. it is like a hint that the package needs flutter to run

That's why we can not convert a Dart package to Flutter one. (We can but we should not)

mtrezza commented 1 year ago

Well, I think we can put this PR on hold for now as further deliberation seems to be needed.