memory_and_json_directories 1.7.0
memory_and_json_directories: ^1.7.0 copied to clipboard
A platform independent directory structure, which can be saved as JSON
memory_and_json_directories #
A Flutter package, with a platform independent directory structure, which can be saved as JSON. This package is an implmentation of a general tree.
Please Post Questions on StackOverflow, and tag @CatTrain (user:16200950) #
MAJNode
naming rules #
- must match the following regex expression:
^[a-zA-Z0-9_]+( [a-zA-Z0-9_]+)*$
- each name must be unique amoungst its peers
- when new nodes are added their name can not match a root node's name, because before they are added to another node as a child, they are a peer of all root nodes
Building a directory #
-
Memory Building #
-
The method used to display the directory for the end user
-
From JSON
-
pass a valid JSON string to
MAJNode.fromJson
- the node created will be the root of the directory
- all other nodes can be found from the root node, or
MAJProvider.map
-
example
MAJNode root = MAJNode.fromJson('[{"name":"the rooter","path":"/the rooter","parent":"","typeName":"maj_directory","data":{}}]');
-
-
From Objects
-
create nodes and add them as children to other nodes
-
It is reccomended you don't make a single node the child of two different nodes
-
example
MAJNode root = MAJNode( name: "the rooter", typeName: MAJDirectory.typeName, child: MAJDirectory(), ); MAJNode two = root.addChild( MAJNode( name: "Two", typeName: MAJDirectory.typeName, child: MAJDirectory(), ), ); two.addChild( MAJNode( name: "deep", typeName: MAJDirectory.typeName, child: MAJDirectory(), ), );
-
-
-
JSON Building #
-
The method used to save the directory to a file system or server
-
using the reference to the root node run
breadthFirstToJson
, which will convert the entire directory to JSON -
example
// root must have been already created before this is run, see above String saveMe = root.breadthFirstToJson(); // run code to save the json string ...
-
Custom Items #
-
items that can be displayed in the directory, saved to JSON, and built from JSON
-
create an object that
implements MAJItemInterface
-
getTypeName
must return a string which is unique to all of your custom items of the same type- all of your custom items will have the same
typeName
unless they are a different type of custom item - it is reccomended to add a
static const String typeName
property to the object
- all of your custom items will have the same
-
build
- passes the current build context, and a reference to the node that contians the custom item
- must return a
Widget
- the
Widget
should get and store all of it's data innodeReference.data
this way the data can be saved as JSON. Any data not saved innodeReference.data
will not be saved as JSON
- the
-
example
class CustomItem implements MAJItemInterface { static const String typeName = "custom_item"; @override String getTypeName() { return typeName; } @override Widget build({ required BuildContext context, required MAJNode nodeReference, }) { return CustomItemWidget( // created by you nodeReference: nodeReference, ); } }
-
-
create a widget to display your custom item
- example
- see below how data is read and written to
nodeReference.data
class CustomItemWidget extends StatelessWidget { final MAJNode nodeReference; // recommend always adding const CustomItemWidget({ Key? key, required this.nodeReference, // recommend always adding }) : super(key: key); @override Widget build(BuildContext context) { return Column( children: [ // a back button to navigate back to the parent widget ElevatedButton( child: const Text("back"), onPressed: () { context.read<MAJProvider>().navigateToByNode( nodeReference.parent!, ); }, ), TextFormField( initialValue: nodeReference.data!["name"], // get custom data onChanged: (value) { nodeReference.data!["name"] = value; // set custom data }, ), ], ); } }
- see below how data is read and written to
- example
-
create a definition used to build the custom item from JSON
-
must be done before any attempt to build from JSON, or the build will fail
-
if the definition already exists an error will be thrown if
MAJNode.addDefinition
is used to add the definition -
example
MAJNode.addDefinition( typeName: CustomItem.typeName, function: () { return CustomItem(); }, );
-
-
add the custom item to a directory
-
add the new node as a child of a existing node usually
-
example
MAJNode root = MAJNode( name: "the rooter", typeName: MAJDirectory.typeName, child: MAJDirectory(), ); root.addChild( MAJNode( name: "custom", typeName: CustomItem.typeName, data: <String, dynamic>{ "name": "Snarf", }, child: CustomItem(), ), );
-
MAJProvider.map
#
-
this map, maps node paths to node references in memory only
-
this map allows accessing nodes O(1) when you have the node's path
-
Adding Entries automatically #
MAJNode
- when the node is first created it is added to the map as a root entry
- ex:
{"/myNodeName": nodeReference, ...}
- if a node with the same path already exists in
MAJProvider.map
it is overwritten by default- if
MAJNode(safeAddToMap: true)
then an error will be thrown if a node already exists in the map with the same path
- if
- ex:
- when the node is first created it is added to the map as a root entry
MAJNode.addChild
- the existing path for the node is removed from
MAJProvider.map
- ussually the root path described above
- the new path is then added to
MAJProvider.map
- if a path that is the same as the new path exists in
MAJProvider.map
it will be overwritten
- the existing path for the node is removed from
-
Adding Entries manually #
MAJProvider.addToMap
-
adds a entry to
MAJProvider.map
, by default it overwrites existing entries- unless
MAJProvider.addToMap(check: true)
in which case an error will be thrown if an entry already exists
- unless
-
example
MAJProvider.addToMap( path: "/root/myCustomPath", node: myNodeReference, // MAJNode );
-
-
Removing Entries automatically #
MAJNode.removeChild
- when a child node is removed it and all of it's children are removed from
MAJProvider.map
- when a child node is removed it and all of it's children are removed from
MAJNode.remove
- removes the current node and all it's children from
MAJProvider.map
- removes the current node and all it's children from
-
Removing Entries manually #
MAJProvider.removeFromMap
-
removed an entry from the map, returns null if that entry doesn't exist to be removed from
MAJProvider.map
-
example
MAJProvider.removeFromMap( path: "/root/myCustomPath", );
-
Nesting in Objects That Will be Converted to Json #
-
create an object which the root can be nested in
-
example
/* A sample object to illustrate nesting in an object, which will be converted to json */ class ConvertToJson { String token; MAJNode root; ConvertToJson({ required this.token, required this.root, }); /// convert to json ref /// https://docs.flutter.dev/development/data-and-backend/json /// still need to call jsonEncode /// ex: /// jsonEncode(convertToJson.toJson()); Map<String, dynamic> toJson() { return { "token": token, "root": root.breadthFirstToArray(), }; } }
-
-
convert the root node to a array of objects stored in breadth first order
- see in above example
root.breadthFirstToArray()
- see in above example
-
convert to json
-
example
ConvertToJson convertToJson = ConvertToJson( token: "Some token", root: root, ); jsonEncode(convertToJson.toJson());
-
Storage #
-
Memory Storage #
- The directory is stored as a general tree
- Each node has
children
- array of nodes which are the children of this node
- if the array is empty the node has no children
parent
- a reference to the node which is this node's parent
- if
null
this means the node is a root node
name
- a
String
that is the name of the node - must be unique amongst its peers
- a
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
- a
typeName
- a
String
, which is used to determine which definition to use when building the node from json
- a
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
- A
child
- an
object
, which implmentsMAJItemInterface
- the object builds a widget when it's
build
method is called by the node
- an
-
JSON storage #
- The directory is stored as an array of json objects
- in left to right, top to bottom order (breadth first)
- each json object contains
name
- see
name
in memory storage
- see
path
- see
path
in memory storage
- see
typeName
- see
typeName
in memory storage
- see
parent
- the path of the current node's parent
- if is empty the node is a root node
data
- a JSON object contaning the data required to build the node
- Example
[{"name":"the rooter","path":"/the rooter","parent":"","typeName":"maj_directory","data":{}}, ...]
- The directory is stored as an array of json objects