I am a very newbie in Dart and have big trouble to understand the "shortcut" of Dart code.
One of them is the "(...) {...}".
Could you take a look at the attached screenshot and help me to understand what are the "(..)" in blue and red rectangles?
Thank you!
child: Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
print(isSwitched);
});
},
activeTrackColor: Colors.lightGreenAccent,
activeColor: Colors.green,
),
These functions are called anonymous functions.
The onChange function took a function as an argument. And run that function with an argument it already has.
void fn(value) {
// setState code
}
// which you can use as
onChanged: fn,
But declaring a new function just to use at that one place can be tiresome and inefficient. That's why anonymous functions are useful. So you can write the previous code with anonymous functions as
onChanged: () { // the same as function fn but with no name or declaration
// setState code
}
The same goes for setState function. But it takes a function with no arguments.
Related
I have a method 'activity' which hosts a 'screen' and since state hoisting is cool, I did just that but with a problem.
While the code works perfectly (incrementing the value) using Modifier.clickable, the same code does not properly work using detectTapGestures::onTap as can be observed through Log.d("ExampleScreen", "onClick Multi Count is $multiCount")
#Composable
fun ExampleActivity() {
var multiCount by remember {
mutableStateOf(0)
}
Log.d("ExampleActivity", "Multi Count is $multiCount") //this works either way
ExampleScreen(
multiCount = multiCount,
incrementMultiCount = {
multiCount = ++multiCount
}
)
}
#Composable
fun ExampleScreen(
modifier: Modifier = Modifier,
multiCount: Int,
incrementMultiCount: () -> Unit
) {
Log.d("ExampleScreen", "Example Multi Count is $multiCount") //this works either way
Column(
modifier = modifier
.fillMaxSize()
.padding()
) {
Text(
modifier = Modifier
.size(100.dp, 50.dp)
.background(MaterialTheme.colorScheme.primaryContainer)
.clickable {//this works
incrementMultiCount()
Log.d("ExampleScreen", "onClick Multi Count is $multiCount")
}
/* .pointerInput(Unit) { //this does not work
detectTapGestures(
onTap = {
incrementMultiCount()
Log.d("ExampleScreen", "onClick Multi Count is $multiCount") //this stays 0
},
onLongPress = {
}
)
}*/, text = "Click to confirm"
)
}
}
Partially, it works the same (incrementing works but not reading the
value) as shown in logs defined in ExampleActivity and function body of
ExampleScreen but doesn't in the onTapGesture.
If the remember value is directly in the ExampleScreen composable, onTap and clickable works perfectly but not what I wanted.
Finally, before suggesting I use what works, I wanted to use detectTapGestures because I really need the LongPress method for a secondary work.
Plus I will really appreciate an explanation since I thought both works the same.
You have to wrap your callback in rememberUpdatedState, something like this:
val onTap: () -> Unit = {
incrementMultiCount()
Log.d("ExampleScreen", "onClick Multi Count is $multiCount")
}
val updatedOnTap = rememberUpdatedState(onTap)
detectTapGestures(onTap = { updatedOnTap.value.invoke() })
This is what .clickable modifier does with its onClick argument as well, you can see that in its source code. Explanation can be found in rememberUpdatedState documentation:
rememberUpdatedState should be used when parameters or values computed during composition are referenced by a long-lived lambda or object expression. Recomposition will update the resulting State without recreating the long-lived lambda or object, allowing that object to persist without cancelling and resubscribing, or relaunching a long-lived operation that may be expensive or prohibitive to recreate and restart.
By the way, there is also Modifier.combinedClickable that can be used for long click detection.
I have a problem that I still can't solve and it just doesn't want to work. Basically I have to convert a function into a composable.
In the old function I launched a Coroutine and at the result, I changed context and then continued with my processes. In compose I don't understand how I have to "change context" in order to continue.
Old code:
fun getMyView( activity: Activity
) {
backgroundCoroutineScope.launch {
//some stuff here
withContext(coroutineContext) {
startSearchView(
activity
)
}
}
}
New not working code:
#Composable
fun getMyView( content: #Composable() () -> Unit) {
LaunchedEffect(key1 = Unit) {
//some stuff here like old funciont
//here I don't know how to change context, wait the end and go ahead. startSearchViewis a composable function too
// i want to use it to populate my screen
startSearchView(
content
)
}
}
How can I solve it? Thanks
Seems like you are trying to asynchronously "create" composable function, but UI emitting doesn't work this way. Like #PylypDukhov suggested, you should keep a mutable state holding nullable result of your async action. After loading the data set this state. Then in composable just do something like:
if (data != null) {
SearchComposable(data)
}
This way the composable will be emitted after the data is loaded
I'm trying to write a fully reactive hello-world example in Flutter, specifically, a simple counter. Here, I have a null-stream from a "+1" button which gets triggered on button press:
class ReactiveButton extends StatelessWidget {
final _controller = StreamController();
Stream<void> pressedEvent;
final text;
ReactiveButton({this.text = ""}) {
pressedEvent = _controller.stream;
}
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => _controller.sink.add(null),
child: Text(text),
);
}
}
I want to convert that stream into a sequence of natural numbers to feed into an RxText. So I use the zipWith method from rxdart with a tight stream of natural numbers, discarding the null value. Now the problem is that using the stream of natural numbers created by a tight while (true) loop results in an infinite cycle because (presumably) the stream never returns control between yields:
Stream<int> naturals() async* {
int count = 0;
while (true) {
yield count++;
}
}
Alternatively, I can use Stream.periodic constructor with a close to zero period. However, this results into my application gradually becoming sluggish (presumably, due to the streams indefinitely counting in vain).
So what I need is a lazy stream that only produces values when asked (immediately). That actually sounds like an Iterable but for Iterable I don't have a method to zip it with a stream. I thought about implementing a function which takes a list of streams/iterables and returns a stream of lists, working more or less like the regular zipper. It seems rather complicated though, and I'm not sure I can write a production-ready implementation.
The question is: what are my options? Is there a ready-to-go solution for this? Should I at all try to go full reactive with Flutter?
I have a class called A which is a Stateless class and I have a class called B which is a Stateful class
The build method of A class is as follows
#override
Widget build(BuildContext context) {
return BlocProvider(
bloc: DashboardListBloc(),
child: Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
SafeArea(child: _dashboardAppBar(context)),
SizedBox(
height: 10.0,
),
B() // this is class B
],
),
)),
);
I have declared my bloc object in B class
Suppose in body of class A, i wrap SingleChildScrollView with RefreshIndicator, so how in its refresh property
i am supposed to call the methods of Bloc class whose reference are defined in class B.
I thought of moving everything to class B and removing class A but
that causes another problem as i have to initialise Bloc in init method and as init is called before build,
bloc will always result in null as i will be using BlocProvider InheritedWidget in build method of class B
You should create one single bloc instance and provide it to both classes.
In other words your bloc should not be created in class B but instead you can create your bloc by wrapping any common parent of A and B (such as your App widget) with a BlocProvider and using the create method. Then in any child widget you can do BlocProvider.of<MyBloc>() or context.read<MyBloc>() and this way both classes A and B will share the same Bloc instance.
Suppose there is a widget with a method controlling visibility animation, toggleVisibility(). In a BLoC pattern, I want to use a stream to invoke that function. I find this pretty tricky.
Since it is an animation rather than a complete redraw, a StreamBuilder doesn't fit.
Manually add a listener to the BLoC streams isn't convenient either.
In initeState() function of the target widget, we don't have a context, so it's hard to get a reference to the BLoC.
Edit: This is not the case after I read RĂ©mi Rousselet's answer. We can access the context even outside of build() function, because State<T> class has a property named 'context' and is documented in Flutter's docs.... I wasn't aware of that.
In build(context) function of the target widget, we have the context. But the widget can be frequently re-built, so you have to manually clean the outdated subscriptions. Otherwise it will create tons of garbages.
A hack with StreamBuilder can do, since the StreamBuilder has implemented all the subscription and unsubscription functionalities. Insert a StreamBuilder somewhere in the layout of the target widget.
StreamBuilder(
stream: Bloc.of(context).stream,
builder: (context, snapshot){
toggleVisiblity();
return Container():
}
);
But this is really a hack. It mixed layout with logic and introduced a useless widget which could cause layout bugs.
So I wonder if there is a good way of doing this in flutter.
You cannot use StreamBuilder to do that. You have to manually listen to the stream
class Example extends StatefulWidget {
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
StreamSubscription subscription;
#override
void didChangeDependencies() {
super.didChangeDependencies();
Stream stream = Bloc.of(context).someStream;
subscription?.cancel();
subscription = stream.listen((value) {
// do something
});
}
#override
void dispose() {
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}