memory_and_json_directories

Last updated:

0 purchases

memory_and_json_directories Image
memory_and_json_directories Images
Add to Cart

Description:

memory and json directories

memory_and_json_directories #
A Flutter package, with a platform independent directory structure, which can be saved as JSON. This package is an implementation of a general tree.
Please Post Questions on StackOverflow, and tag @CatTrain (user:16200950) #
https://stackoverflow.com/
Basic Example #
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:memory_and_json_directories/memory_and_json_directories.dart';

void main() {
// build a tree in memory
MAJNode root = MAJNode(
name: "root",
child: MAJDirectory(),
);
root
.addChild(
MAJNode(
name: "one",
child: MAJDirectory(),
),
)
.addChild(
MAJNode(
name: "one one level down",
child: MAJDirectory(),
),
);
root.addChild(
MAJNode(
name: "two",
child: MAJDirectory(),
),
);

// convert to json
String treeAsJson = jsonEncode(
root.breadthFirstToJson(),
);

// load from json string
MAJNode fromJson = MAJNode.breadthFirstFromJson(
jsonDecode(
treeAsJson,
),
);

// display the tree loaded from json
runApp(
MaterialApp(
home: Scaffold(
body: MAJBuilder(
root: fromJson,
),
),
),
);
}
copied to clipboard
Custom Item Example #


see example for full code


requires Basic Example code


create a custom item

ensure it implements MAJItemInterface
this example has a back button that navigates to the parent node, and text that displays "Hello I am a custom item"

/// A basic custom item that can display whatever you wish
class CustomItem implements MAJItemInterface {
/// not required, but recommended
static const String typeName = "custom_item";

@override
String getTypeName() {
return typeName;
}

@override
Widget majBuild({
required BuildContext context,
required MAJNode nodeReference,
}) {
return Center(
child: Column(
children: [
// back button, navigates to parent, unless there is not parent node
ElevatedButton(
onPressed: () {
if (nodeReference.parent != null) {
context.read<MAJProvider>().navigateToByNode(
nodeTo: nodeReference.parent!,
);
}
},
child: const Text("Back"),
),

// custom widget to display
const Text("Hello I am a custom item"),
],
),
);
}
}
copied to clipboard


add the custom item's definition, so it can be built from json
// define the custom item, so it can be loaded from json
MAJNode.addDefinition(
typeName: CustomItem.typeName,
function: () => CustomItem(),
);
copied to clipboard


add a custom item to the tree
// add a custom item to the tree
root.addChild(
MAJNode(
name: "Custom Item",
child: CustomItem(),
),
);
copied to clipboard


Data and Shared Data Example #

see example for full code
requires Basic Example, and Custom Item Example Code
modify CustomItem


set code in build to set a default data value
// set default data value if no data
if (nodeReference.data == null || nodeReference.data!.keys.isEmpty) {
nodeReference.data = nodeReference.data = <String, bool>{
"pressed": false,
};
}
copied to clipboard


wrap returned Widget in a StatefulBuilder to allow dynamic button changes


add text that shows the shared data for CustomItem
Text(
nodeReference.getSharedData().toString(),
),
copied to clipboard


add button that shows the data for the instance of CustomItem
// add button that shows the data for the instance of CustomItem
OutlinedButton(
// update data value for instance on pressed to opposite of what it was
onPressed: () {
setState(() {
nodeReference.data!["pressed"] =
!nodeReference.data!["pressed"];
});
},
// display data for this instance
child: Text(
nodeReference.data!["pressed"].toString(),
),
),
copied to clipboard


set shared data for CustomItem
// set shared data for custom Item in memory
// will be loaded from json below
MAJNode.setSharedData(
typeName: CustomItem.typeName,
data: {
"data": "Shared Data value",
},
);
copied to clipboard




Multiple Trees Example #


see example for full code


requires Basic Example, Custom Item Example, and Data and Shared Data Example code


create shared data for CustomItem in the second tree

see how a mapKey is set, this is so the name space doesn't collide with the first tree

// set shared data for custom Item in memory
// will be loaded from json below
// map key for tree 2 set
MAJNode.setSharedData(
typeName: CustomItem.typeName,
data: {
"data": "Shared Data value from tree 2",
},
mapKey: "tree_2",
);
copied to clipboard


create a second tree, and show how it can be converted to json, and built in memory from json

see how a mapKey is set, this is so the name space doesn't collide with the first tree

// create 2nd tree's root
MAJNode root2 = MAJNode(
name: "root 2",
child: MAJDirectory(),
mapKey: "tree_2", // because second tree, avoids naming collisions
);
root2
.addChild(
MAJNode(
name: "one",
child: MAJDirectory(),
mapKey: "tree_2",
),
)
.addChild(
MAJNode(
name: "One down from one",
child: CustomItem(),
mapKey: "tree_2",
),
);
copied to clipboard


add a second MAJBuilder

see how a mapKey is set, this is so the name space doesn't collide with the first tree

// tree 2, see how it has a map key, this is so the name space
// doesn't collide with the first tree. tree 1 uses the default map key
MAJBuilder(
root: fromJson2,
mapKey: fromJson2.mapKey,
),
copied to clipboard


Building a Custom Directory Item #


see example for full code


requires Basic Example, Custom Item Example, Data and Shared Data Example code, and Multiple Trees Example


Build CustomDirectory, Widget, and State
/// An example of how to create a custom directory, which will display
/// in a manner you desire
class CustomDirectory implements MAJItemInterface {
/// not required, but recommended
static const String typeName = "custom_directory";

@override
String getTypeName() {
return typeName;
}

@override
Widget majBuild({
required BuildContext context,
required MAJNode nodeReference,
}) {
// return custom directory widget
return CustomDirectoryWidget(
nodeReference: nodeReference,
context: context,
);
}
}

/// widget that displays the custom directory
class CustomDirectoryWidget extends StatefulWidget {
final MAJNode nodeReference;
final BuildContext context;

// get node reference, and context to pass to the state
const CustomDirectoryWidget({
super.key,
required this.nodeReference,
required this.context,
});

@override
State<StatefulWidget> createState() {
return CustomDirectoryWidgetState();
}
}

/// state of the custom directory
class CustomDirectoryWidgetState extends State<CustomDirectoryWidget> {
@override
Widget build(BuildContext context) {
/// builds the buttons that are displayed in the directory widget
List<Widget> buildButtons() {
List<Widget> temp = [];

// add back button
temp.add(
// back button, navigates to parent, unless there is not parent node
ElevatedButton(
onPressed: () {
if (widget.nodeReference.parent != null) {
context.read<MAJProvider>().navigateToByNode(
nodeTo: widget.nodeReference.parent!,
);
}
},
child: const Text("Back"),
),
);

// add children of the current directory
for (int i = 0; i < widget.nodeReference.children.length; i++) {
temp.add(
// build the node when button pressed
OutlinedButton(
onPressed: () {
context.read<MAJProvider>().navigateToByNode(
nodeTo: widget.nodeReference.children[i],
);
},

// display node's path
child: Text(
widget.nodeReference.children[i].path,
),
),
);
}

return temp;
}

// return column of buttons that when pressed load nodes
return Column(
children: buildButtons(),
);
}
}
copied to clipboard


add definition
// define the custom directory, so it can be loaded from json
MAJNode.addDefinition(
typeName: CustomDirectory.typeName,
item: () => CustomDirectory(),
);
copied to clipboard


add to tree
// add custom dirs
root2.addChild(
MAJNode(
name: "Custom Dir 1",
child: CustomDirectory(),
mapKey: "tree_2",
),
)
// add to custom 1
..addChild(
MAJNode(
name: "Custom Dir 1",
child: CustomDirectory(),
mapKey: "tree_2",
),
)
// add to custom 1
..addChild(
MAJNode(
name: "Custom Dir 2",
child: CustomDirectory(),
mapKey: "tree_2",
),
// add to custom 2
).addChild(
MAJNode(
name: "Custom Dir 3",
child: CustomDirectory(),
mapKey: "tree_2",
),
);
copied to clipboard


MAJNode #


Naming rules #

MAJNode.name must match the following regex expression: ^[a-zA-Z0-9_]+( [a-zA-Z0-9_]+)*$
each name must be unique amongst its peers

when new nodes are added their name can not match a root node's name in their tree, because before they are added to another node as a child, they are a peer of all root nodes
These rules are scoped to within the MAJProvider.maps map the nodes are referenced in





Stored in memory and json, see storage below for data


MAJNode is the node used in the general tree


See code for functions


MAJItemInterface #

The interface used to ensure children of MAJNode have the correct functions
it is recommended to add a static type name variable, but it is not required

ex: static String typeName = "my_custom_item";


String getTypeName

must return a string which will be a unique key in MAJNode.definitions map


Widget majBuild

return a widget which you wish to have displayed
nodeReference contains any data you would need from a constructor



MAJBuilder #

This is the widget used to display your tree in memory
makes user of Provider to receive change notifications from MAJProvider

this is how the widget knows which MAJNode to display


This widget takes two parameters
required MAJNode root

The node you wish to have displayed first when the widget builds
This is typically the root of the tree


String mapKey = MAJProvider.defaultMapKey

the key you use to reference the map of nodes
don't set if you only intend to have one tree
set if you wish to have more than one tree in memory at once



MAJProvider #

The ChangeNotifier that notifies MAJBuilder to display a MAJNode
Only exists in memory
static const defaultMapKey = "default";

the default map key used in MAJProvider.maps


static Map<String, Map<String, MAJNode>> maps

The outer map references a unique key for each tree

if there is only one tree the default map key is used


If there is to be more than one tree in memory at a time, ensure each tree has a key in the outer map
The inner maps contain the path of each node in the tree, and a memory reference to that node, so any node can be accessed O(1)


MAJNode references can be added and removed manually to MAJProvider.maps, but it is not recommended

static void addToMap
static MAJNode? removeFromMap


allows navigation to MAJNode by path or reference

by path

navigateTo

uses map key provided by MAJBuilder to determine, which tree the node is in
ex: context.read<MAJProvider>().navigateTo("/root/node");




by MAJNode reference

navigateToByNode

navigates to the node regardless of which tree it is in
ex: context.read<MAJProvider>().navigateToByNode(myNode);







Storage #


Memory Storage #

The directory is stored as a general tree
All nodes have access to MAJNode.sharedData

static Map<String, Map<String, dynamic>> sharedData
Outer map

keys refer to mapKey used by MAJProvider.maps to differentiate between trees in memory
the keys in the outer map must match the ones used in MAJProvider.maps


Inner maps implements MAJItemInterface

keys must be a type name of a item that
the data is any json serializable data
can be applied to all nodes of that type in the same tree

tree differentiated using outer map






Each node has

name

a String that is the name of the node
must follow MAJNode naming rules above


path

a String that contains the node's name and the name of all it's ancestors
must be unique amongst all nodes in the tree


parent

a reference to the node which is this node's parent
if null this means the node is a root node


children

array of nodes which are the children of the node
if the array is empty the node has no children


typeName

a String, which is used to determine which definition to use when building the node from json


child

an object, which implements MAJItemInterface
the object builds a widget when it's build method is called by the node


data

A Map<String, dynamic>
stores data required to build the node's child
can be null if not required
must be able to convert to a JSON object

number, boolean, string, null, list or a map with string keys




mapKey

a String that identifies, which tree the node belongs to
must match a key used in MAJProvider.maps outer map







JSON storage #

The directory is stored as an object with nodes and shared data.

sharedData

a map containing data shared within each tree
see MAJNode.sharedData in memory storage


nodes

an array of json objects stored in in left to right, top to bottom order (breadth first)
Each node in the array has:

name

see name in memory storage


path

see path in memory storage


typeName

see typeName in memory storage


parent

the path of the current node's parent
if is empty the node is a root node


data

a JSON object containing the data required to build the node
see data in memory storage


mapKey

see mapKey in memory storage








ex: {"nodes":[{"name":"root","path":"/root","parent":"","typeName":"maj_directory","mapKey":"default","data":{}}],"sharedData":{}}



References #

JSON
memory
Semantic Versioning 2.0.0
breadth first
general tree
big O notation
flutter JSON and serialization
regex
Provider
ChangeNotifier

License:

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.