display a markdown file in flutter app and use the reference links - flutter

I am trying to take a markdown file, contents.md, and then display that on a page in my app, but I want to be able to use the reference links that I have added that point to different files, chapter1.md, chapter2.md, chapter3.md, and so on. I have been able to display markdown formatted from contents.md, but the links don't work.
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Flutter Markdown"),),
body: FutureBuilder(
future: rootBundle.loadString("assets/manual/contents.md"),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Markdown(data: snapshot.data);
}
return Center(
child: CircularProgressIndicator(),
);
}),
);
}
Is there a way to do it? Because Google hasn't helped at all and I am seriously doubting that it is possible.

I had to create a variable outside of the class, and then make a getter and setter for it, then make the future builder a method and call setState(){} after setting the variable.
String file = "contents.md";
class _ManualState extends State<Manual> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(getFile()),),
body: displayMarkdown(getFile())
);
}
FutureBuilder<String> displayMarkdown(String file){
return FutureBuilder(
future: DefaultAssetBundle.of(context).loadString
("assets/manual/" + file),
builder: (BuildContext context, AsyncSnapshot<String> snapshot){
if (snapshot.hasData) {
return Markdown(data: snapshot.data, onTapLink: (link){
setFile(link);
setState((){});
},
);
}
return Center(
child: CircularProgressIndicator(),
);
},
);
}
}
String getFile() {
return file;
}
String setFile(String name) {
file = name;
return file;
}

Flutter has great support for additional libraries. There is a library for markdown files as well, you can find it here.

Related

Flutter: get bool value from snapshot

I am trying to show an on-boarding screen so to do so I call an async function where I get a Future<bool> weather this is the first time the user launched the app. Since its a Future value I have decided to use FutureBuilder.
Here is part of the code of what I am trying to do:
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: FutureBuilder(
future: firstLaunch, //Future<bool> value
builder: (context, snapshot) {
if (snapshot.hasData) {
// What I would like to do is use either one of these lines
// return _firstLaunch ? const OnboardingScreen() : const WelcomePage();
// return snapshot.data ? const OnboardingScreen() : const WelcomePage();
} else {
return const WelcomePage();
}
},
),
);
}
The issue is that I am unable to use _firstLaunch within the FutureBuilder since its still considered a future value and snapshot.data does give me the correct value when I print it but the type is AsyncSnapshot<Object?> so I also cant seem to use it.
Does anyone have suggestions on how to solve this or possibly know of a better way to show the on-boarding screen?
Prefer documentation:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Try this:
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: FutureBuilder<bool>(
future: firstLaunch, //Future<bool> value
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
return snapshot.data ? const OnboardingScreen() : const WelcomePage();
} else {
return const WelcomePage();
}
},
),
);
}
Use a bool inside has data
if (snapshot.hasData) {
bool check = snapshot.data;
return check ? const OnboardingScreen() : const WelcomePage();
}

In Flutter, is there a way get item in flutter secure storage in widget?

I want to take a data from flutter secure storage. But as you know its a async function for get it. So in my widget , how can i take that data ? I cant use await for take it. So how can i take it ? Thanks for response!
Code :
FutureBuilder<PurchasedPackageResponseModel?>(
future: service.getPurchasedPackages(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
I want to take data here ==> bool isClientTherapist = (I cant use await here So its giving error.) UserSecureStorage.getField("isClientTherapist");
final model = snapshot.data!.data!;
if (model.isEmpty) {
return const NotBoughtSessionWidget();
}
return const SizedBox();
If i make it stateful widget i can take in initstate. But i using getx and i want to take in stateless widget. Is it possible ?
You can simply add another FutureBuilder inside the first one to await this second call.
FutureBuilder<PurchasedPackageResponseModel?>(
future: service.getPurchasedPackages(),
builder: (context, snapshot1) {
if (!snapshot1.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return FutureBuilder<bool?>(
future: UserSecureStorage.getField("isClientTherapist"),
builder: (context, snapshot2) {
if (!snapshot2.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
final isClientTherapist = snapshot2.data!;
final model = snapshot1.data!.data!;
if (model.isEmpty) {
return const NotBoughtSessionWidget();
}
return const SizedBox();
},
);
},
)

The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. / Flutter

I just started writing with Flutter.I am constantly getting this error. What should i do? Here is my code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<WeatherInfo>(
future: futureWeather,
builder: (context, snapshot) {
if (snapshot.hasData) {
} else if (snapshot.hasError) {
return Center(
child: Text("${snapshot.error}"),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<WeatherInfo>(
future: futureWeather,
builder: (context, snapshot) {
if (snapshot.hasData) {
} else if (snapshot.hasError) {
return Center(
child: Text("${snapshot.error}"),
);
}
return const Center(child: CircularProgressIndicator());
}));
}
Inside the FutureBuilder you covered the case in which you have the data or you have an error, but not when you are expecting for the future to complete(And don't have neither the data or an error).
I just added a Circular progress indicator to be shown while no data or no error are returned from the Future, that should prevent the FutureBuilder from returning null. And when the snapshot state changes the data or error would be shown.
I think you should try and wrap the future builder in a container

ScanStreamTransformer alternative that's called once per event instead of once per listener

I think, now I see what's going on with respect to transform() in Bloc.dart class. ScanStreamTransformer inside the constructor of BloC.dart is called once for each listener (there are 3 listeners inside the One.dart class so it's called 3 times). I'd like to change this behaviour so that it's called once per event, regardless of how many listeners attached e.g. calling _mainBloc.inValue(widget.value) would invoke transform only once, right now it's called 3 times because there are 3 listeners (see streamBuilder()) inside the One.dart build() function.
I hope this is a little bit more clearer compared to previous post (now deleted).
class OneState extends State<One>{
#override
void didChangeDependencies() {
super.didChangeDependencies();
_mainBloc.inValue(widget.value);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
streamBuilder(),
streamBuilder(),
streamBuilder()
],
),
);
}
streamBuilder(){
return StreamBuilder(
stream: _mainBloc.values$,
builder: (context, AsyncSnapshot<Map<String, Future<String>>> snapshot){
if(snapshot.connectionState == ConnectionState.waiting) return Center(child: Container(child: new CircularProgressIndicator()));
if(!snapshot.hasData) return Center(child: Container(child: Text("No Data"),));
return FutureBuilder(
future: snapshot.data[widget.value],
builder: (contextFuture, AsyncSnapshot<String> snapshotFuture){
if(snapshotFuture.connectionState == ConnectionState.waiting)
return Center(child: Container(child: new CircularProgressIndicator()));
return Center(
child: Container(
child: Text(snapshotFuture.data),
),
);
},
);
},
);
}
}
BloC
class MainBloc{
final ApiRequest _provider;
MainBloc(this._provider){
values$ = _value.stream.transform(
ScanStreamTransformer((Map<String, Future<String>> cache, String symbol, index){
print('transformer');
cache[symbol] = _provider.fetchData();
return cache;
},
<String, Future<String>>{},
));
}
final _value = BehaviorSubject<String>();
Observable<Map<String, Future<String>>> values$;
Function(String symbol) get inValue => _value.sink.add;
dispose(){
_value.close();
}
}
Add a .asBroadcastStream(). Like:
values$ =_value.stream.transform(
ScanStreamTransformer((Map<String, Future<String>> cache, String symbol, index){
print('transformer');
cache[symbol] = _provider.fetchData();
return cache;
},
<String, Future<String>>{},
)).asBroadcastStream();
It should stop the triple-time calling.

Is nested StreamBuilder good pattern?

I am fetching articles from HackerNews API using Bloc Pattern and Streams.
I am loading all the articles and presenting in the UI with the help of a stream builder, and this works fine.
Now I wrapped the article fetching Stream builder with the new loading StreamBuilder.
Now when the loading stream builder has true (means it is loading) it shows a circular progress indicator or else, it shows the child (Article List wrapped with a Stream Builder).
This works fine. But it is bugging me that I have wrapped Stream builder inside a stream builder. I know I can take help of rxdart but I am just not sure how.
I tried to add a loader with the help of snapshot.hasData or not but that didn't work, so I decided to create another stream and subject that takes a bool and tells the UI if it is loading or not.
Code fetching data int the bloc:
_getAndUpdateArticles(StoryType storyType) {
_isLoadingSubject.add(true);
getListIds(storyType).then((list) {
getArticles(list.sublist(0, 10)).then((_){
_articleSubject.add(UnmodifiableListView(_articles));
_isLoadingSubject.add(false);
});
});
}
UI:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder(
stream: widget.hnBloc.isLoading,
builder: (context, snapshot) {
if (snapshot.data) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) => ListView(
children: snapshot.data.map(_buildItem).toList(),
),
);
}
},
),
.........
EDIT
I have tried this, but this isn't working:
StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
);
} else {
return CircularProgressIndicator();
}
}
),
I Don't think there is a complete way to avoid nested StreamBuilders. I personally wouldn't consider it a bad practice, but it will definitely lead to more build.
In your case, You can modify your hnBloc to emit a single state that can be a loading state or data state , thereby eliminating the need for a nested StreamBuider.
eg.
StreamBuilder<HnState>(
stream: hnBloc.currentState,
initialData: HnLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is HnLoadingState) {
return Center(child: CircularProgressIndicator());
}if (snapshot.data is HnDataState) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
),
}
},
)
This pattern is very common when using the flutter_bloc package. You can see a basic example of this here to understand it better.