Skip to content

ParseLiveSliverListWidget implementation #642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
AsyraniAzni opened this issue Jun 12, 2021 · 2 comments
Closed

ParseLiveSliverListWidget implementation #642

AsyraniAzni opened this issue Jun 12, 2021 · 2 comments

Comments

@AsyraniAzni
Copy link

I've not worked with Slivers yet.
But as ParseLiveList handles all of the fetching etc., adding something based on SliverList should work.
Actually I've planed working on LiveList again. Since I am not happy with some of the scrolling behavior when adding/removing items at the top of the list. Maybe working with Slivers will provide more flexibility.

Originally posted by @fischerscode in #330 (comment)

Sorry, maybe I miss something. Have you done implemented ParseLiveSliverListWidget? I did not find any reference. Sorry, if I miss something

@d3roch4
Copy link

d3roch4 commented Aug 23, 2021

This code work for me, I replace 'AnimatedList' to 'SliverAnimatedList':

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk.dart' as sdk;

typedef ChildBuilder<T extends sdk.ParseObject> = Widget Function(
    BuildContext context, sdk.ParseLiveListElementSnapshot<T> snapshot);

typedef StreamGetter<T extends sdk.ParseObject> = Stream<T> Function();
typedef DataGetter<T extends sdk.ParseObject> = T Function();

class ParseLiveListSliver<T extends sdk.ParseObject> extends StatefulWidget {
  const ParseLiveListSliver({
    Key key,
    this.query,
    this.listLoadingElement,
    this.queryEmptyElement,
    this.duration = const Duration(milliseconds: 300),
    this.scrollPhysics,
    this.scrollController,
    this.scrollDirection = Axis.vertical,
    this.padding,
    this.primary,
    this.reverse = false,
    this.childBuilder,
    this.shrinkWrap = false,
    this.removedItemBuilder,
    this.listenOnAllSubItems,
    this.listeningIncludes,
    this.lazyLoading = true,
    this.preloadedColumns,
  }) : super(key: key);

  final sdk.QueryBuilder<T> query;
  final Widget listLoadingElement;
  final Widget queryEmptyElement;
  final Duration duration;
  final ScrollPhysics scrollPhysics;
  final ScrollController scrollController;

  final Axis scrollDirection;
  final EdgeInsetsGeometry padding;
  final bool primary;
  final bool reverse;
  final bool shrinkWrap;

  final ChildBuilder<T> childBuilder;
  final ChildBuilder<T> removedItemBuilder;

  final bool listenOnAllSubItems;
  final List<String> listeningIncludes;

  final bool lazyLoading;
  final List<String> preloadedColumns;

  @override
  _ParseLiveListSliverState<T> createState() => _ParseLiveListSliverState<T>(
    query: query,
    removedItemBuilder: removedItemBuilder,
    listenOnAllSubItems: listenOnAllSubItems,
    listeningIncludes: listeningIncludes,
    lazyLoading: lazyLoading,
    preloadedColumns: preloadedColumns,
  );

  static Widget defaultChildBuilder<T extends sdk.ParseObject>(
      BuildContext context, sdk.ParseLiveListElementSnapshot<T> snapshot) {
    Widget child;
    if (snapshot.failed) {
      child = const Text('something went wrong!');
    } else if (snapshot.hasData) {
      child = ListTile(
        title: Text(
          snapshot.loadedData?.get<String>(sdk.keyVarObjectId) ??
              'Missing Data!',
        ),
      );
    } else {
      child = const ListTile(
        leading: CircularProgressIndicator(),
      );
    }
    return SliverToBoxAdapter(child: child);
  }
}

class _ParseLiveListSliverState<T extends sdk.ParseObject>
    extends State<ParseLiveListSliver<T>> {
  _ParseLiveListSliverState(
      {this.query,
        this.removedItemBuilder,
        bool listenOnAllSubItems,
        List<String> listeningIncludes,
        bool lazyLoading = true,
        List<String> preloadedColumns}) {
    sdk.ParseLiveList.create(
      query,
      listenOnAllSubItems: listenOnAllSubItems,
      listeningIncludes: listeningIncludes,
      lazyLoading: lazyLoading,
      preloadedColumns: preloadedColumns,
    ).then((sdk.ParseLiveList<T> livelist) {
      setState(() {
        _noData = livelist.size == 0;
        _liveList = livelist;
        livelist.stream.listen((sdk.ParseLiveListEvent<sdk.ParseObject> event) {
          final AnimatedListState animatedListState =
              _animatedListKey.currentState;
          if (animatedListState != null) {
            if (event is sdk.ParseLiveListAddEvent) {
              animatedListState.insertItem(event.index,
                  duration: widget.duration);

              setState(() {
                _noData = livelist.size == 0;
              });
            } else if (event is sdk.ParseLiveListDeleteEvent) {
              animatedListState.removeItem(
                  event.index,
                      (BuildContext context, Animation<double> animation) =>
                      ParseLiveListElementWidget<T>(
                        key: ValueKey<String>(
                            event.object.get<String>(sdk.keyVarObjectId) ??
                                'removingItem'),
                        childBuilder: widget.childBuilder ??
                            ParseLiveListSliver.defaultChildBuilder,
                        sizeFactor: animation,
                        duration: widget.duration,
                        loadedData: () => event.object as T,
                        preLoadedData: () => event.object as T,
                      ),
                  duration: widget.duration);
              setState(() {
                _noData = livelist.size == 0;
              });
            }
          }
        });
      });
    });
  }

  final sdk.QueryBuilder<T> query;
  sdk.ParseLiveList<T> _liveList;
  final GlobalKey<AnimatedListState> _animatedListKey =
  GlobalKey<AnimatedListState>();
  final ChildBuilder<T> removedItemBuilder;
  bool _noData = true;

  @override
  Widget build(BuildContext context) {
    final sdk.ParseLiveList<T> liveList = _liveList;
    if (liveList == null) {
      return widget.listLoadingElement ?? SliverToBoxAdapter(child: Container());
    } else {
      return buildAnimatedList(liveList);

      return Stack(
        children: <Widget>[
          if (widget.queryEmptyElement != null)
            SliverAnimatedOpacity(
              opacity: _noData ? 1 : 0,
              duration: widget.duration,
              sliver: widget.queryEmptyElement,
            ),
          buildAnimatedList(liveList),
        ],
      );
    }
  }

  @override
  void setState(VoidCallback fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  Widget buildAnimatedList(sdk.ParseLiveList<T> liveList) {
    var animatList = SliverAnimatedList(
      key: _animatedListKey,
      initialItemCount: liveList.size,
      itemBuilder:(BuildContext context, int index, Animation<double> animation) {
        return ParseLiveListElementWidget<T>(
          key: ValueKey<String>(liveList.getIdentifier(index)),
          stream: () => liveList.getAt(index),
          loadedData: () => liveList.getLoadedAt(index),
          preLoadedData: () => liveList.getPreLoadedAt(index),
          sizeFactor: animation,
          duration: widget.duration,
          childBuilder:
          widget.childBuilder ?? ParseLiveListSliver.defaultChildBuilder,
      );
    });

    if(widget.padding != null)
      return SliverPadding(
        padding: widget.padding,
        sliver: animatList,
      );
    return animatList;
  }

  @override
  void dispose() {
    _liveList?.dispose();
    _liveList = null;
    super.dispose();
  }
}

class ParseLiveListElementWidget<T extends sdk.ParseObject>
    extends StatefulWidget {
  const ParseLiveListElementWidget(
      {Key key,
        this.stream,
        this.loadedData,
        this.preLoadedData,
        this.sizeFactor,
        this.duration,
        this.childBuilder})
      : super(key: key);

  final StreamGetter<T> stream;
  final DataGetter<T> loadedData;
  final DataGetter<T> preLoadedData;
  final Animation<double> sizeFactor;
  final Duration duration;
  final ChildBuilder<T> childBuilder;

  @override
  _ParseLiveListElementWidgetState<T> createState() {
    return _ParseLiveListElementWidgetState<T>(
        loadedData, preLoadedData, stream);
  }
}

class _ParseLiveListElementWidgetState<T extends sdk.ParseObject>
    extends State<ParseLiveListElementWidget<T>>
    with SingleTickerProviderStateMixin {
  _ParseLiveListElementWidgetState(DataGetter<T> loadedDataGetter,
      DataGetter<T> preLoadedDataGetter, StreamGetter<T> stream) {
    _snapshot = sdk.ParseLiveListElementSnapshot<T>(
        loadedData: loadedDataGetter != null ? loadedDataGetter() : null,
        preLoadedData:
        preLoadedDataGetter != null ? preLoadedDataGetter() : null);
    if (stream != null) {
      _streamSubscription = stream().listen(
            (T data) {
          setState(() {
            _snapshot = sdk.ParseLiveListElementSnapshot<T>(
                loadedData: data, preLoadedData: data);
          });
        },
        onError: (Object error) {
          if (error is sdk.ParseError) {
            setState(() {
              _snapshot = sdk.ParseLiveListElementSnapshot<T>(error: error);
            });
          }
        },
        cancelOnError: false,
      );
    }
  }

  sdk.ParseLiveListElementSnapshot<T> _snapshot;

  StreamSubscription<T> _streamSubscription;

  @override
  void setState(VoidCallback fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  @override
  void dispose() {
    _streamSubscription?.cancel();
    _streamSubscription = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final Widget result = SizeTransition(
      sizeFactor: widget.sizeFactor,
      child: AnimatedSize(
        duration: widget.duration,
        vsync: this,
        child: widget.childBuilder(context, _snapshot),
      ),
    );
    return result;
  }
}

@RodrigoSMarques
Copy link
Contributor

Closed. No activity in the last 14 days.
If necessary open again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants