Follow US:

Practice English Speaking&Listening with: Dart-side and Dark-side (The Boring Flutter Development Show, Ep. 34)

Normal
(0)
Difficulty: 0

[BEEP]

[MUSIC PLAYING]

EMILY: Hello, and welcome back to "The Boring Show."

I'm Emily.

KATERINA: I'm Katrina.

EMILY: Welcome, Katerina.

Katerina is a GDE for Flutter.

And we are so thrilled that you can join us

on "The Boring Show."

KATERINA: Thank you for inviting me.

I'm happy to be here.

EMILY: We're happy to have you.

So today, we are going to be looking at the Hacker News app

again.

And Katerina was looking at the code base

and pointed out a couple of issues.

For starter-- well, two things we're going to focus on today--

cleaning up the code base a little bit, and then also,

we're going to finally implement dark mode.

KATERINA: Yay.

EMILY: So let's talk a little bit about some of the issues

in the code base that you mentioned.

KATERINA: Yes, we use error--

we throw errors and we throw exceptions.

And sometimes we misunderstand what we should do.

And if we have--

if our app has a state that expects it to be sometimes,

we should throw an exception.

Because it can happen.

And if we have something that it's not expected to be,

for example, when we divide by 0, we should--

it's an error.

And here in Hacker News app, sometimes we throw error.

Sometimes we throw exception.

EMILY: Yeah, I admit, I am guilty of this.

I actually have to look this up every single time I do it.

Because in my head, often, the idea of error and exception

are synonyms.

But as Katerina points out, Dart makes the distinction

between errors and exceptions.

So when you, the developer, are writing an API

or dealing with some sort of problem,

that unexpected problem, the correct terminology

for what you use varies depending

on the type of problem.

KATERINA: Yes, for example, if you create a package and people

access your API are on--

for example, you multiply numbers,

and someone sends you strings--

it's an error.

And if something is predictable and can happen, it's exception.

EMILY: Do you have an example of an exception-type thing?

KATERINA: For example, when we get data from the internet

and we start to parse it, it can fail sometimes.

And it doesn't depend on us.

EMILY: Right, so the input was all correct, but something--

there was something unexpected.

Like, it was malformed XML or something.

KATERINA: Right.

And if we try to get an item in array out of range,

it's an error.

It's something that the program does wrong and fails.

EMILY: Right, so it's kind of like if it's, you,

the programmer's, fault, or me, the programmer's, fault, then

it's an error.

Because I used--

I should have known-- or I should

have checked, for example, in the case

of an array, the length of the array before accessing

the 11th element out of a 10-element array.

But if it's like, I did everything right.

I called to get this XML.

I parsed it.

And then instead, this API is giving me a 503 forbidden

thing when I try to make that request, it's an exception.

Because, yeah, it was just an unexpected thing.

KATERINA: Right.

EMILY: Yeah.

All right, that makes sense.

That makes total sense.

So let's clean up our code base.

So let's see here, I'm going to use good old grep and grep

for error unless you remember, off the top

of your head, where are those spots that I should look.

KATERINA: It's probably in worker.dart.

EMILY: worker.dart?

KATERINA: We can look out for error and for exception.

EMILY: Here we go, worker.dart.

OK.

Exception-- all right.

So this is based on statusCode.

That seems correct.

KATERINA: Yes.

EMILY: Got some more--

article not parsable.

Connection failed-- sure, those all seem good.

KATERINA: And now let's look up, probably, for-- oh, socket.

EMILY: Speaking of-- yes, so look at this.

We have a socket exception.

And then we decided to throw in HackerNewsApiError.

That might be--

I might have been part of the naming of that.

So I claim responsibility.

So we should rename this, yeah?

KATERINA: Yeah, it should be exception.

EMILY: And so we-- do we want to extend.

KATERINA: I think we have, already, a class for exception.

We just can change it.

We have HackerNewsApiException.

EMILY: Oh, turns out I can't spell "Hacker News API."

There we go.

It is in hackernewsapi.dart.

KATERINA: In second exception, here

we can throw HackerNewsApiException

at some parameters.

EMILY: Got it.

KATERINA: Because it's already implemented.

EMILY: OK, so just switch this to exception.

And let's look at the parameters for it.

You need a status code and a message.

KATERINA: It's probably optional.

We can use a message.

EMILY: Oh yeah, I see that.

Oh, it's just named.

That's why it's complaining.

OK, message added.

And then response status code is 200.

That should be an exception too, yeah?

KATERINA: Right.

EMILY: OK.

So exception message-- cool.

Anywhere else?

Le's look for error.

KATERINA: Yes.

EMILY: In that file or somewhere else?

KATERINA: In worker.dart, we have a DeserializationError.

EMILY: Mm-hmm.

KATERINA: But actually, what is it?

Probably, we can--

EMILY: Le's jump to-- oops, that was supposed to be--

KATERINA: Because if it happens, if we deserialize data

from somewhere and it's common to us, and it can be--

it should be, probably--

EMILY: Yeah.

KATERINA: --exception.

EMILY: So this is serializer.dart,

which we didn't--

this isn't built by you.

KATERINA: Mm-hmm.

EMILY: You're looking up deserialization.

KATERINA: Ah, there is an issue.

EMILY: Should be an exception.

KATERINA: So we can leave it here, HackerNewsApiException,

as it is, unchanged.

EMILY: Yeah, so they're saying that, actually, that API,

someone else also mixed up the distinction.

But the issue is still open.

So perhaps in the future, we will get

that API cleaned up as well.

KATERINA: Great.

EMILY: Were there any other spots that we should look at?

KATERINA: I think that's it.

EMILY: OK, great.

Well, that was relatively painless.

And now I just need to remember that exceptions are pretty

much what you should be doing.

Because if you are creating errors, that's--

you would probably create errors--

you would create an error class more frequently

if you're writing an API for someone else to call.

But otherwise, just like amongst your own classes,

you're probably not going to be creating

a class that extends errors.

KATERINA: Right.

EMILY: And you're doing it wrong.

KATERINA: Yes, it's no fault of an app.

And it must be fixed by a developer.

EMILY: Yes, you should throw an error on the fact

that you're writing an error class, yes.

Cool.

So dark mode, eh?

KATERINA: All right.

OK.

EMILY: So this--

I am super excited that we're finally

getting back to dark mode.

If you have been watching for a while,

you may recall that Emily and I added this silly little drawer

thing with the dream of having a favorites

page, a settings page, and adding dark mode

in that settings page.

And you can see that it doesn't look very great right now.

So we have a fair bit to do.

But first, let's actually implement dark mode.

KATERINA: Yes.

And should we implement dark mode at the beginning

and change it in Android settings?

Or will we create an item in drawer to change it?

EMILY: Oh, that's a good point.

So that's actually a good question

that I don't know the answer to in terms of what

is considered best practices.

I know-- I only have my own experience.

We can do an internet search.

So obviously, if the user specifies

dark mode for their entire OS--

for example, Android-- then I want my app to respect that.

KATERINA: Right.

EMILY: But I think, also apps--

I know, for example, the Twitter mobile app, on the app level,

you can say, I want this particular app to be dark mode.

So which would you like to start with?

KATERINA: Let's start with dark mode,

and change it in Android settings.

EMILY: OK--

KATERINA: And after that--

EMILY: --the OS level.

KATERINA: --yeah--

EMILY: That's great.

KATERINA: --we can create item in drawer.

EMILY: Sounds good.

Yeah, because in fact, this will dig more

into respecting your operating system's preferences.

And then you can do the one-off thing per app.

So where do we go about doing that?

KATERINA: I think we can go to theme data

and add dark theme there.

EMILY: Yes.

So this is actually where all this Material Design theming

stuff is going to finally pay off.

KATERINA: And yeah, MaterialApp.

EMILY: And then jump to the definition.

I know there-- I thought it was on theme-- oh, here we go.

So MaterialApp has a parameter called darkTheme.

So we just set that to true/false

depending on what our Android things are setting, yeah?

KATERINA: Yes, we can use ThemeData.dark() and set

darkTheme.

EMILY: darkTheme-- ThemedData.dark().

That's it?

KATERINA: It seems yes.

Let's see how it works.

EMILY: Yeah.

Now, OK, I'm going to set it in my Android thing.

But I'm going to caution everyone that, by doing this,

I'm probably going to lose the connection.

Because normally, when you-- if you

start navigating to other apps, then the hot reload thing

gets a little angry with you.

So it's possible I might have to restart my app again--

sigh.

OK, so just type in "dark mode?"

KATERINA: Yeah, we should go to settings of device and change

theme.

EMILY: Come back, settings.

Katerina, this is embarrassing.

I'm not up on the latest Android OS.

How do I get to the settings?

KATERINA: Yeah, we can--

EMILY: Scroll down?

KATERINA: --scroll down.

And again, scroll down.

And here we have settings.

EMILY: OK, and dark theme.

KATERINA: Yes.

EMILY: Phew, I was worried there for a second.

OK, dark theme, turn on.

OK, so we've got--

whoa, what just--

Android.

Turn on.

And nothing-- or do I need to restart it?

KATERINA: Probably, we should, yeah, restart it.

EMILY: OK, let's just kill this.

OK.

Start afresh.

So while we wait for this to start up, Katerina,

what is your favorite thing to talk about as a GDE?

I know you go around and give presentations and things,

or write articles.

What is your topic that you're most interested in right now,

would you say?

KATERINA: Last talk I gave, it was about state management

and about bloc.

And so I went through some topics and some cases

where bloc can be not a very good choice.

EMILY: Yeah.

KATERINA: I went through some edge cases.

EMILY: Look at that.

This is dark mode.

Whoo!

Well, that was really painless.

KATERINA: Yeah, we can probably--

EMILY: Test it out a little bit?

Let's see, let me add a favorite.

KATERINA: And try new stories.

It works.

And what about drawer?

EMILY: Mmm-- white.

KATERINA: Yes.

EMILY: Also, there's nothing there.

Wait, before we get too in-depth,

do you want to do the quick summary of what

are the use cases when you shouldn't use bloc,

just for people to know?

KATERINA: In my opinion, from my experience, when

we have an app where we have a lot of input fields,

and we have validation for every input field,

it can be a lot of streams and syncs [INAUDIBLE]

to implement this business logic.

EMILY: So many things to type.

KATERINA: Yeah, because we need to validate for every input

field and send, also, validation.

EMILY: Yeah, that can get pretty overwhelming.

KATERINA: Yes.

And as a example, when we implement app,

and we move to another screen, and we want to return back,

sometimes, user provided some information on the screen.

And that, we need to save.

And when user presses back, because all those saving

calls can be sometimes asynchronous, we don't get--

EMILY: Oh, you miss it.

Just, the timing is such that it's not updated in time.

KATERINA: Yes, and sometimes, imagine

that we want to save the data, and it doesn't pass validation,

or error occurred during saving.

But user already left the screen.

EMILY: Ah, I see.

Oh no, yeah, that's a problem.

KATERINA: Yes.

And actually, when user presses the Back button,

we can rope our scaffold to his WillPopScope widget.

And onWillPop callback, we can prevent them from going back.

EMILY: Until it's returned and validated.

KATERINA: Until the data is--

was saved to the database, and so probably, some confirmation

dialogs also if there are some validation errors.

EMILY: Cool, those are all good reasons.

Thank you for the quick summary.

All right, so back to dark mode.

So we've got this sad, little drawer

that is not obeying dark mode.

Oh, we can also check the search thing.

Oh, that works.

KATERINA: Yeah, that looks pretty good.

EMILY: We should look--

because I don't know, actually.

Should the search bar also dark mode--

be in dark mode?

I don't know.

We could look.

We'll do the drawer first.

But a few things to clean up, but we're close.

So that's pretty cool.

All right, so this drawer--

KATERINA: We can have a look what we have in drawer.

EMILY: Yes.

Where do we have our drawer?

It's been a little bit since I posted this code.

There we go.

Oh, look at that.

That might be a problem.

KATERINA: Yes.

EMILY: Why did we do that?

We'll never know.

KATERINA: We can remove it and see what happens.

EMILY: Oh, well, that was easy.

Is that really all there is to it?

So we just randomly hard-coded that?

Should we check-- should we test it back to white

and see if there's a problem?

KATERINA: Yes, let's check light mode, what we changed.

EMILY: Yeah.

All right, and then we're going to have to start up again.

So this is a little [INAUDIBLE].

Why is my--

KATERINA: [INAUDIBLE].

EMILY: Yeah, it's being a little finicky.

All right, let's kill that again.

Let's see what else is going on here, just

to make sure that we don't have any other color things that

are--

doesn't look like we've got any.

I'll search colors dot--

refresh indicator.

That's OK.

That can be.

Because that's just the little primary color.

Where do we use primary color?

Theme data-- yeah, that seems OK.

Canvas color-- I don't know what Canvas color is.

Oh, I just pressed F5 again.

No!

OK, I've got to start again.

Sorry.

We were so close to building.

Canvas color-- jump to definition.

Whoa, what is this?

This is part of theme data.

Doesn't really explain what it is.

But we can-- oh, here we go.

Default color--

KATERINA: --of Canvas.

EMILY: Canvas-- well, obviously.

All right, OK, so we're back.

White-- oh, no!

KATERINA: And we have Canvas color, and it's black.

EMILY: Oh, so this is a Canvas, possibly?

KATERINA: Yes.

Do we need this Canvas color?

EMILY: No.

I don't know why we have it.

Let's do a quick search to see if I'm

using Canvas anywhere else.

Everyone is learning how much I grep for--

it's development with Emily.

No, we literally just used that I don't know why we did that.

Let's get rid of it.

That's so much better.

KATERINA: Yes, and we don't need to hard-code a color.

EMILY: So there's one thing to take away

is that, in a world where people can customize your themes,

you should not hard-code.

Just generally, hard-coding is bad is probably

the main takeaway that, probably, no one needed

us to show.

But here we are, illustrating it further.

KATERINA: Let's have a look in the app.

EMILY: Yeah.

KATERINA: Probably, something was changed.

EMILY: Oh, I see.

KATERINA: Bottom bar.

EMILY: Oh my gosh, I just did that again.

What is going on?

App isn't available.

KATERINA: We didn't see bottom bar.

EMILY: Oh, it just went away.

I see.

KATERINA: So this color was used in bottom bar.

EMILY: Mm-hmm.

Let's look a little bit about this Material Canvas--

Material Canvas color.

Oh, that's not useful.

Material Design?

There we go.

Well, that's Flutter.

But I'm just curious about Canvas in general.

Canvas color-- I guess that's just a Flutter thing.

I think we can improve our documentation in this area.

Let's see, Canvas-- rectangle.

That's really not very helpful at all, is it?

KATERINA: So Flutter paints on Canvas.

EMILY: Mm-hmm.

KATERINA: So we can choose a color of Canvas

where we paint elements.

EMILY: So depending on whether it's dark theme or light theme,

we'll just select one versus the other, you're suggesting?

KATERINA: Mm-hmm.

EMILY: OK, yeah.

KATERINA: So now, probably, we need

to fix a color for bottom--

EMILY: Yes.

KATERINA: --navigation.

EMILY: And I'm going to try and hot reload.

But I won't be surprised if it didn't.

Because I was messing with the app

switching and did weird things on Android.

So let's see here, back to--

so are you suggesting, in ThemeData,

just having Canvas color, and then detecting whether it's

dark, and setting it?

Or what do you recommend?

KATERINA: I think--

I've never used Canvas color when I was developing apps.

Probably, we need to specify color

for more than navigation bar.

And let's see what we have in both the navigation.

Probably, we don't have color or--

what is there?

Oh yeah, probably, it will work as well.

EMILY: I'm curious.

All right, that's-- do we have ThemeData.isDark()?

Return to ThemeData.

Primary color dark?

Let's search again.

Media query.

KATERINA: Mm-hmm.

EMILY: OK, good old media query.

Platform brightness-- so let's just test this.

We can also do what you were suggesting as well.

But I'm curious about this too.

Why does it not like that?

Because this is in a constructor.

And it's like [INAUDIBLE].

Oh, you can also do Theme.brightness.

Let's try that.

Is this going to work?

I feel skeptical, because we are constructing the theme right

here.

Yeah, that's not going to work.

OK, what were you suggesting?

KATERINA: I suggest to check what colors we

have in bottom navigation bar.

Probably, there is something there.

EMILY: Doesn't look like there's anything set.

Are you suggesting setting something, specifically, there?

KATERINA: Let's try it.

EMILY: OK.

Let's get our app back.

So what do we have here?

We've got this smaller--

background colors.

KATERINA: We can try the ground color

and see what happens if we set it to black, whether it

will be black or not.

EMILY: OK.

And here's where the hot reload might be a little-- oh,

it did work.

All right, but now we've got this black thing

on a white theme.

So can we test for dark mode here?

KATERINA: Yes.

EMILY: Yeah?

So let's try what I was just searching.

Theme dot of context brightness--

It doesn't like that this is not known at compile time.

KATERINA: Probably, we should compare it to brightness

dark or brightness light.

Because it's not a Boolean.

EMILY: Oh.

Oh my gosh.

That's why that was happening.

Oh my gosh.

Sometimes I'm a little slow.

So OK, equals brightness dot dark.

And then if so, then we can call it black.

OK, so we can do that up--

sometimes I am-- oh, all right, so that's the background color.

But that's-- we're back where we started.

Why don't we do--

KATERINA: Can you style, somehow--

EMILY: The text?

KATERINA: --bottom navigation?

EMILY: That is what this is.

KATERINA: Ah, OK.

EMILY: This is bottom navigation.

Is there a different property we should look at?

I mean, we could do the text as well.

But I thought-- fonts--

selectedItemColor, unselectedItemColor.

fixedColor-- what's that?

Oh, fixedColor is a backwards compatibility thing,

so I don't need that.

So there's selectedItemColor and unselectedItemColor.

KATERINA: It's probably when we select.

EMILY: Right, which one versus the other one.

So we could specify that, hard-code.

I don't love that.

But we can do it to start with, I guess, in which case,

maybe we want to pull this out.

That was a big build method that we should refactor.

Dark mode.

OK, so what was I--

I was looking at selectedItemColor.

Or do we want--

well, OK.

So they're both currently white, I guess.

Copy this logic.

This should be a little action.

OK.

KATERINA: Do we want to change color if item is selected?

Or should we just have black?

EMILY: Black or-- what do you mean?

KATERINA: So before, we had our bottom bar navigation,

and it was black-colored.

EMILY: Right.

KATERINA: So should we leave it?

How will it look in dark scene?

EMILY: Right, well, that's why I was

saying we test whether it is.

But then we have to specify selectedItemColor

and unselectedItemColor, which I don't love.

KATERINA: Mm-hmm.

EMILY: I don't know.

This is fine, but--

OK, and then I'll test dark mode, just for--

KATERINA: It looks good--

EMILY: Yeah.

KATERINA: --in dark scene.

And what about drawer?

Did we fix it?

Yes.

EMILY: What surprises me is the selected item color

we specified to be white here, and it's green.

Is it time to pull out the inspector?

Because I'm not sure what's going on.

Do you know what's going on?

KATERINA: No.

EMILY: Well, let's open the DevTools.

KATERINA: Should we call setState() when we--

no.

EMILY: No, because there's nothing

changing in the program itself.

Let's select the widget, make this smaller.

Bottom navigation bar-- let's look at that.

I thought the icon would be simple.

Let's look at text.

Where do we have style?

Size, traction-- am I missing something?

Do you see it?

KATERINA: Probably, we should have a look on this box

where we have--

not on text.

No, we still have--

EMILY: Because it expands.

I don't know if we're going to be able to get that.

There we go.

So that's the semantics thing.

But that's a bottom navigation tile.

What are you looking for on that one?

KATERINA: I try to find some styles.

EMILY: No style.

Well, clearly, the search is not working here, because there's--

KATERINA: I see only icon theme, but it's not something we need.

EMILY: Yeah.

OK, how about we table this?

And I will ask the people.

And we'll come back with the answer next time.

I do want to--

I think I just hit the wrong thing.

I do want to--

let's see, so when I was looking at doing Canvas color up top,

that would only change these things here.

Let me stop the widget--

or the select widget.

KATERINA: Right, into also, this color was used

to in drawer as default color.

EMILY: Right.

And so now, it's in dark mode, because we haven't specified it

in drawer, right?

So it's better if we can put it up top.

KATERINA: Right.

EMILY: Right?

So let us--

I just-- we need to pull this.

This file is getting big.

I'm going to--

Canvas color.

And we were using Theme.

And let me remove this.

Interesting.

Did I type something wrong?

If the dark mode, then--

KATERINA: Probably, we check it's wrong somehow, how to--

EMILY: So what are you saying?

KATERINA: Is it a right way to check whether we are in--

EMILY: --dark mode or not?

KATERINA: Mm-hmm.

EMILY: I believe so.

Looked like you could do MediaQuery.of() or the themes--

use the theme itself.

KATERINA: Mm-hmm.

EMILY: Oh, maybe-- yeah, maybe.

Because we haven't constructed a dark mode theme.

Was it platform brightness?

Mm-mm.

Let me-- just for grins, I'm going to restart this.

Because I'm a little bit skeptical.

If not, let's see--

KATERINA: So we want-- if we are in dark theme,

we want to have black color.

EMILY: Yes.

KATERINA: And if we're in light theme, we want to have white.

EMILY: Right.

KATERINA: But probably, it's good to have, always, black.

So as it was before, the color of bottom navigation was black.

EMILY: Oh, it was?

I thought it was white originally.

KATERINA: No, it was black.

And I was thinking that we will have

the same color for the bottom bar and for the main screen.

And we won't see where--

EMILY: --what is going on.

KATERINA: And I think black color will be--

EMILY: Oh, no.

Oh, because-- I think this is because--

yes, Material context.

The context has not been built yet.

But let's try Theme.

This probably won't work either.

Brightness-- oh, that does work.

OK, so it was a connection thing.

KATERINA: Yes.

EMILY: OK, so we have this.

And we've got the white there.

And now, just for grins--

dark theme.

Oh, and it's-- it's not--

ugh.

KATERINA: Now it works as expected.

I mean, Canvas color--

EMILY: And we don't have the--

oh, it was a hot reload thing.

It was a, because we had swapped and it lost the connection, it

was holding old properties, even though it was pretending

like it was hot reloading.

Whoo.

OK, so the moral of the story is,

whenever you swap out to a different app,

don't trust your connection, even if it

pretends like it's normal.

KATERINA: Yes, but I mean, it works as expected.

EMILY: Yes.

KATERINA: It changes.

EMILY: Yes.

KATERINA: But do we want to keep white background color

for bottom bar in light theme?

EMILY: So it really was white when we started?

KATERINA: It was black when we started.

EMILY: Sorry.

Yes, OK.

So then-- oh my gosh, and that's--

KATERINA: Because we--

EMILY: --because we haven't--

KATERINA: --lost connection again.

EMILY: But OK, so to--

so what you're suggesting is-- yeah, so we'll keep it black,

as it originally was when we started in the light theme.

But then, what do you think we should do in dark theme.

It should be black again, probably?

KATERINA: Yes, I think it should be black.

Because it will be two different [INAUDIBLE]

between black color and white color.

EMILY: So then we don't even have

to do this silly if business.

We're just always no.

KATERINA: What about drawer?

EMILY: Right, I see.

And so this is why you were saying

specify on the bottom navigation bar specifically.

KATERINA: Yes, because we wanted black all the time.

EMILY: OK.

Got it.

Sorry, I was a little slow.

This is not my best episode.

So where we were-- bottomNavigationBar.

Always be black.

KATERINA: All right.

EMILY: And we--

KATERINA: We don't need, probably,

the selected item and unselected.

Because it's extra logic.

EMILY: If-- yes, because in dark mode--

but in light mode, will it know to make them white,

make the text white when it's-- because normally,

in light mode, the text is black.

KATERINA: Yes.

EMILY: So will we have to specify it?

I mean, we can test.

KATERINA: Yeah, let's test what we have.

EMILY: This actually is, in a way,

a motivation for implementing it in our--

oh, look at that.

So yeah, it does know that if the color is black,

to do the right thing and change the text.

KATERINA: Yes.

EMILY: So we did it.

KATERINA: But I think it takes its color from primary color.

Because our primary color of the app is white.

EMILY: Yes.

Wait, what takes the color from primary color?

These things?

KATERINA: Yes.

EMILY: Oh, the text.

KATERINA: Yeah.

EMILY: I see.

Yep.

And in dark mode, it gets overwritten to do

that kind of greenish color when it's selected.

All right, so we've got that.

Do we want to add it in our settings thing?

KATERINA: Yes, why not?

Do we have, still, time?

EMILY: Yeah, we've got some time, a little bit of time

left.

And this, we can add it so we can toggle it

with the benefit of not potentially losing our app

connection and being confused.

So let's get to this Settings page.

Maybe I will pull up the inspector, because I don't--

nope, I don't know what I did there.

Command Palette, DevTools--

KATERINA: So how do we implement this?

We will have toggle here for dark theme.

And will we share these sessions in shared preferences?

And when we change it, we change it in Theme.

EMILY: Yes, let me pull up Settings page.

Going to do a grep again.

Oh, in Pages, Settings-- that would be the obvi--

it's the one place that I don't have maximized.

OK, Settings-- yes, it's a very exciting page.

All right, so we'll have a toggle.

And let's just start with that.

I can't remember if we disconnected our thing or not.

Let's see, is it a Switch?

And a Switch takes a value, unchanged.

So we want this to become a stateful widget--

KATERINA: Yes.

EMILY: --so it can hold that value.

Or wait, so you were saying put it in Shared Preferences.

And would we just always read from that then?

KATERINA: Probably, we need to change it immediately.

We need to save it in state management,

probably, in provider.

Because when it changed, we need to get this update immediately.

EMILY: Yes.

Oh, and Shared Preferences is going

to be an asynchronous thing.

OK, so let's look at our good old--

what do we have in provider?

We've got our Preferences.

That seems like a good--

Preference Notifier, Shared Preferences.

So show web view.

Where are we using this?

Nowhere?

There we go.

Oh.

Oh, this is when we tap on--

we're showing an actual web page.

So why are we saving?

Why do we have a Boolean in our--

that seems odd.

I'm going to make a note to come back to this.

Because why is whether we show a web view or not

in a Shared Preferences?

That seems very strange.

OK, meanwhile-- so we can add something

in this change notifier that says user dark mode

or something like that.

KATERINA: Yes, and [INAUDIBLE] preferences to save

for the latest--

to get the latest value--

EMILY: Yes.

KATERINA: --so that we don't come back to light mode

if you don't want to use always.

EMILY: Right.

User dark mode-- and what's going on here?

Why are they passing a--

they're calling-- they're just returning the function,

but they're not passing anything.

Do you see what I'm confused about?

KATERINA: Yes.

EMILY: This class is very strange.

OK, so we're going to ignore that,

whatever they're doing there.

And we want to say, if--

we want to test if the--

first, check the user preferences.

If it's not set, then fall back to the system default.

KATERINA: Yes.

EMILY: But this, we just want to first check.

Well, we can copy a variant of this

for saving dark mode though.

Or wait, we don't even need to--

this just saves all of them.

So we could add another thing that's userDarkMode.

And this is going to say currentPrefs.has--

what are we doing here?

Current preferences-- it's just setting it.

If it's already there--

[INAUDIBLE]

All right, well, we'll add another variable here.

Shouldn't these be optional?

KATERINA: Mm-hmm.

EMILY: And that would be not caps.

KATERINA: User dark mode.

EMILY: Oh, thank you.

Thank you.

And if I can spell "false" correctly-- false.

And these are named now.

So showWebView.

I guess they could be positional,

but I made them named--

oh, well.

userDarkMode-- oh, I see what's going on.

This was just confusing, because we have showWebView.

It's in three places.

OK, so this one is actually reading this property

rather than calling this.

KATERINA: Oh, I see.

EMILY: I was like, why are we calling a setter that returns

whether it's true or not?

OK, and we're about to do the same thing.

So let's call this userSetDarkMode just

to distinguish that a little bit--

userSetDarkMode.

And then here, we will make our own version, our own setter.

This is not ordered correctly, by the way.

I'm going to move these up.

Because your constructor should be after your variables,

but before any other information.

OK, we're really close.

userDarkMode-- and show userSetDarkMode.

Preference state-- oh, and now we want to say showWebView.

We want our current--

maybe we should change this to take an old one.

Or wait, why are we constructing a whole new thing anyway?

Oh, it's saying if it doesn't exist at all.

OK, that's fine.

And then this is userSetDarkMode.

OK, so we got it in our provider.

KATERINA: Yes.

EMILY: Now let's go to our Settings page.

So we can say--

what is the--

PrefsNotifier.of context.

KATERINA: Mm-hmm.

And switch has value into on parameters.

EMILY: We might need to import that.

There we go.

And it's something different.

Where is this used?

Let's find out how they use it.

I see.

I was like, why is-- no one is using this WebView variable,

it would seem like, unless--

ChangeNotifierProvider, PrefsNotifier-- yeah,

I don't think so.

I'm just going to--

Provider.of.

I never get the verbiage right.

Provider-- Provider, PrefsNotifier.

And we have to import Provider.

And it's going to be not happy, because it's--

we want PrefsNotifier dot something,

whatever the value is.

KATERINA: userDarkMode.

And also, we need to implement onChange.

EMILY: So onChanged-- so now, this

is, when you click on that switch, what will happen.

So onChanged, they have a bool, even

though it's only two values--

newValue.

And so we're going to set--

KATERINA: --newValue.

EMILY: Or is it PrefsSnotifier.set?

No, we have to use that particular object, that's

right, that particular version.

So this thing dot set-- or just userDarkMode and then assign.

KATERINA: Mm-hmm.

EMILY: And we have some errors.

userDarkMode is not defined.

Was it userSetDarkMode?

OK, too many positional arguments.

That is because we are creating a new one.

So showWebView.

Oh, wait a minute.

Hold on.

KATERINA: You didn't have getter for useDarkMode somehow.

EMILY: So we want to add that.

And let me just check.

Because I named it slightly differently here.

But we-- yeah, userDarkMode.

Oh, user-- whoo.

Oh my gosh.

OK.

And then, if defined--

KATERINA: userSet.

EMILY: It's my own fault. OK, I think we are in business.

It's possible we lost things, because--

value not null is not true.

What is going on?

Oh--

KATERINA: Probably--

EMILY: --because it's never been set.

And so we need a default value for that.

And it's because we loaded shared preferences before we

had done all these things.

So I think we need to do a hot restart.

KATERINA: Yes.

EMILY: Let's try that.

KATERINA: OK, we have our checklist.

EMILY: Hmm, [LAUGHING] all right, what is--

insertion must not be null, list view int.

What?

What?

Boolean expression must not be null.

What is the Boolean expression?

Do you think it's--

KATERINA: Probably--

EMILY: Should we just kill and start over?

KATERINA: I think we don't initialize our shared

preferences.

Because when we read them, there is nothing.

User doesn't save any preferences.

And we should get them from the system.

EMILY: Yes, but I thought that we had--

I thought there was-- in load shared preferences,

see where we're checking if that variable exists.

And if not, we just return false.

KATERINA: I see.

EMILY: Let me--

I'm just going to--

I just don't trust it.

Because we've been messing with it so much.

While that goes, I'm also going to just wrap this in a little--

I don't know why this isn't a container.

Let's get rid of that.

It seems silly.

But we should wrap this in a column

and put text that just says, "use dark mode."

So it's like, there's a random switch here.

Column text use-- user--

use dark mode.

OK, so settings--

Boolean must not be null.

What is-- there we go.

Refresh indicator.

Preferences show what view?

We are using showWebView.

Boolean expression must not be null.

How did we mess this up?

Because it seems like maybe I somehow

messed up with this shared preference

when I changed things.

But I don't know how I did that.

showWebView says-- so we just look for that value.

And let's just give these default values, yeah?

KATERINA: Yeah.

EMILY: I mean, we're selling them here, but just for grins.

Hot restart.

I'm trying to think, where are these constructed?

Oh, actually, I think I know, maybe, where this happens.

Because this gets constructed at the top of main().

And since I added those optional things,

we may have not initialized them.

I'll look in a second.

All right.

All right, so--

OK, cool.

So what I think happened is, provider, at the top,

it constructs a shared preferences object.

And it wasn't-- we'd only done it in that prefs page we were

[INAUDIBLE].

OK, so we're really close.

Now we just need to actually make use of this value.

KATERINA: Yes.

EMILY: So instead of--

in our main() thing, we should check--

instead of Theme.dark()--

KATERINA: We should check for brightness.

EMILY: Yes, we should check for if this value is

set from the provider thing.

KATERINA: Yes.

EMILY: So we will do Provider.of.

And it's called PrefState, of course.

And users--

KATERINA: Users.

EMILY: And if true, then we want to say Theme.dark().

Wait, this-- no.

Because this checks to see if the system does it.

KATERINA: Yes, and probably, we should check

for brightness in theme data.

I mean, theme data has brightness.

And if brightness is dark--

so if our user set dark mode, we'd change brightness to dark,

if not, brightness light, something like this.

EMILY: So--

KATERINA: Not here.

I think-- OK.

EMILY: What are you suggesting?

KATERINA: I suggest to go to theme data.

And theme data has brightness parameter.

And we can change brightness to dark, light and dark theme.

I think it comes from the Android settings.

EMILY: Oh.

KATERINA: I'm not sure.

EMILY: Right, but wouldn't this then--

the question is, which one gets overwritten, right?

KATERINA: Mm-hmm.

EMILY: Because I think what we want

is we want the user setting to override the Android

settings, potentially.

KATERINA: Right.

EMILY: Where did this go?

Sorry.

So you're saying brightness-- theme data, brightness.

Wait, didn't we have this?

No?

Do we not have brightness already specified?

We'll test this.

KATERINA: Yes, let's see.

EMILY: Whoo, that's fun.

Could not provide provider because provider--

where do we construct a provider?

Is it up ahead?

Run out multi-provider.

Is it consumer?

Is that what we need?

No.

KATERINA: Do we construct this provider here,

like where multi-provider--

EMILY: I thought we did.

No, we don't.

Where does this get constructed?

Good old grep again--

PrefsState.

Oh, that's PrefsNotifier, sorry.

Oh, no, but we want PrefsNotifier, not PrefsState.

Because PrefsState is the underlying thing

of the notifier.

Oh my gosh, what was it?

PrefsNotifier-- I had it.

It still didn't like it.

It's constructed in line 32.

And I'm probably not typing something

correctly if it thinks I have a--

oh, because can't spell "notifier."

userDarkMode-- all right.

Hot restart on that.

KATERINA: Let's try to change it.

EMILY: Yes.

Oh, OK, so that is--

brightness is only changing the text.

KATERINA: Yes.

EMILY: So I think ThemeData.dark() is getting--

let's read up about this.

Let's say, if it's specified, then you

want to use dark theme for--

if it is-- if it's not this, then you do light, light theme.

Otherwise, you do dark.

KATERINA: Yes.

EMILY: Because it's either the--

so if it's not that, do dark theme.

Otherwise, ThemeData.light-- oops, not "lerp."

Oh, it's dot light.

OK, let's try that.

Get rid of all this.

Settings-- dark mode.

KATERINA: Because probably, settings of our phone

are light.

Should we change not dark theme, just theme?

EMILY: Yes.

KATERINA: Because it always takes--

EMILY: Right, this is what dark mode should

look like when it is specified.

I understand now.

OK, so let's undo that.

We're so close.

We're so close.

OK, so theme, we say, is it dark mode?

If so, if it's dark mode or we have the system properties

which already detects that-- so that's just a fallback?

What are we-- if it's dark mode or--

can we just do this again to get the system, do you think,

get the brightness?

We're specifying a scene.

KATERINA: Yeah, let's try.

EMILY: OK.

Or this, then-- it would be nice if we could--

we're going to copy-paste for a minute to test things out.

Get rid of that business.

And actually--

KATERINA: Actually, what does a Theme.dark() does?

Can we have a look what exactly it changes in scene data?

EMILY: Yes.

This thing?

KATERINA: Yes, it changed just brightness to dark.

EMILY: But didn't we just do that?

Yeah, we had that here--

oh, but because we had explicitly

specified Canvas color here.

So we just have to do it in two places, I think.

We need to-- because before--

here, I'll cut this, this horrible, horrible, long-line

thing that I've done.

OK, so we specified that.

And that just made the text essentially go away.

Because it was just--

it wasn't doing this.

But that's because we specified this hard-coding, right?

KATERINA: Let's try to restart it.

Because I'm not sure whether we're still connected.

EMILY: OK.

Yeah, I'll full-on kill it.

Hmm, this might be our problem here.

So we are the text--

well, the text is changing.

It's the background that's not changing.

So we are in dark mode.

It's that text.

The text is respecting our user thing,

but the background is not.

The Canvas, we want that--

let's see.

We want to see, if the theme is dark or you specify dark mode--

I'm inclined to come back to this.

Because we're basically out of time.

We're really close.

KATERINA: Let's check whether dark theme works when

we change Android settings.

EMILY: Oh yeah, good call.

OK, so--

KATERINA: Since this part is implemented.

EMILY: Yes, that part, we know.

Dark theme here.

And that is good.

And we've got our settings.

So we just need to--

so right now, you can see that the Android is overriding

whatever we're doing here.

So we'll tweak that.

But the real question I have is why that background color

is not changing.

But that just means you'll have to tune in next time we

have our "Boring Show" episode.

And we will figure that out.

So thank you so much for joining us today, Katerina.

KATERINA: Thank you for inviting me.

EMILY: Yeah.

Do you have any other last things

that you would like to share with our Flutter programming

audience?

KATERINA: Flutter is great.

EMILY: That wasn't me trying to, like,

require you to boast about things.

But I was just curious.

Yeah, where can they find you, or talks, or any materials?

If they have questions about bloc or anything like that,

how can they find you?

KATERINA: They can ask me questions on Twitter.

EMILY: Do you want to tell them your Twitter handle?

KATERINA: Yeah, @kate_sheremet.

EMILY: OK.

KATERINA: And also, I have articles on Medium.

So yeah.

EMILY: And be sure to applaud Katerina.

She's great, written good stuff.

KATERINA: Thank you.

EMILY: Cool, well, thank you so much.

And until next time, we will see you again.

[MUSIC PLAYING]

The Description of Dart-side and Dark-side (The Boring Flutter Development Show, Ep. 34)