DEV Community

Mago Acadêmico
Mago Acadêmico

Posted on

2 1

Dark Theme com React Navigation + Typescript + React Native Paper

Nesse artigo você irá aprender a como implementar temas claros e escuros e utiliza-los em todo seu aplicativo com React Native, React Navigation, React Native Paper e Typescript.

Você também pode ver esse tutorial em video e o repositório no Github.

Criando projeto React Native com Typescript

Para começar um projeto React Native com Typescript, basta rodar o seguinte comando.

npx react-native init NavigationTypescriptPaper --template react-native-template-typescript
Enter fullscreen mode Exit fullscreen mode

Referência da documentação

Instalando React Navigation

Para instalar o React Navigation precisamos instalar os seguintes pacotes

yarn add @react-navigation/native react-native-screens react-native-safe-area-context
Enter fullscreen mode Exit fullscreen mode

E dependendo do tipo de navegação que você usar, você instala apenas o pacote para aquele tipo. Para usarmos um Stack, precisamos instalar

yarn add @react-navigation/stack
Enter fullscreen mode Exit fullscreen mode

Caso vocês esteja usando Mac, rode o comando

npx pod-install ios
Enter fullscreen mode Exit fullscreen mode

E para Android, você precisa editar o arquivo MainActivity.java que fica em android/app/src/main/java/<nome do projeto>/MainActivity.java

import android.os.Bundle;
// ...

public class MainActivity extends ReactActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(null);
  }

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Referência da documentação

Instalando React Native Paper

Para instalar o React Native Paper precisamos instalar os seguintes pacotes

yarn add react-native-paper react-native-vector-icons
Enter fullscreen mode Exit fullscreen mode

Referência da documentação

Caso você esteja usando Mac, edite sei PodFile e adicione o seguinte código

pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
Enter fullscreen mode Exit fullscreen mode

E para Android, adicione a seguinte linha no arquivo android/app/build.gradle

apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
Enter fullscreen mode Exit fullscreen mode

Referência da documentação

Criando o ThemeContext e ThemeContextProvider

Primeiro iremos importar os temas tanto do React Navigation como do React Native Paper

import {
  DarkTheme as NavigationDarkTheme,
  DefaultTheme as NavigationDefaultTheme,
  NavigationContainer,
} from '@react-navigation/native';
import {
  DarkTheme as PaperDarkTheme,
  DefaultTheme as PaperDefaultTheme,
  Provider as PaperProvider,
} from 'react-native-paper';
Enter fullscreen mode Exit fullscreen mode

E assim iremos dar um merge nos temas, criando um tema light com a junção dos dois temas default e um tema dark com a junção dos dois temas dark.

const lightTheme = {
  ...NavigationDefaultTheme,
  ...PaperDefaultTheme,
  colors: {
    ...NavigationDefaultTheme.colors,
    ...PaperDefaultTheme.colors,
  },
};

const darkTheme = {
  ...NavigationDarkTheme,
  ...PaperDarkTheme,
  colors: {
    ...NavigationDarkTheme.colors,
    ...PaperDarkTheme.colors,
  },
};
Enter fullscreen mode Exit fullscreen mode

Assim teremos os dois temas definidos com os valores default dos dois pacotes e poderemos adicionar nossas próprias cores caso desejado.

Em seguida já podemos definir 2 tipos que usaremos sobre o nosso tema. O primeiro é criar um tipo que definira o nosso tema, utilizando o typeof do lightTheme, assim caso adicionemos alguma configuração a mais no nosso tema, ele é refletido para o tipo.

export type Theme = typeof lightTheme;
Enter fullscreen mode Exit fullscreen mode

E também definiremos os tipos de temas que teremos, que no caso será light e dark.

export type ThemeType = 'dark' | 'light';
Enter fullscreen mode Exit fullscreen mode

Assim já podemos definir quais dados teremos no nosso context. Passaremos o tema atual, assim como seu tipo, um booleano indicando se o tema é dark, para facilitar a comparação na hora de utilizar, uma função para alternar o valor do tema e outra para atualizar diretamente o tema caso seja necessário.

export interface ThemeContextValue {
  theme: Theme;
  themeType: ThemeType;
  isDarkTheme: boolean;
  toggleThemeType: () => void;
  setThemeType: React.Dispatch<React.SetStateAction<ThemeType>>;
}
Enter fullscreen mode Exit fullscreen mode

E assim utilizaremos React.createContext para criar o contexto e passaremos valores default para cara propriedade.

export const ThemeContext = React.createContext<ThemeContextValue>({
  theme: lightTheme,
  themeType: 'light',
  isDarkTheme: false,
  setThemeType: () => {},
  toggleThemeType: () => {},
});
Enter fullscreen mode Exit fullscreen mode

Como vamos utilizar hooks, já podemos criar o nosso próprio hook que chamaremos de useTheme, simplesmente para facilitar a utilização desse contexto.

export const useTheme = () => useContext(ThemeContext);
Enter fullscreen mode Exit fullscreen mode

Agora iremos para a implementação do context, onde criaremos um componente ThemeContextProvider e a interface para suas props.

export interface ThemeContextProviderProps {
  children: React.ReactNode;
}

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Dentro dele utilizaremos o useColorScheme para saber se o celular está no modo normal ou dark mode e passaremos esse valor para um useState onde armazenaremos o tipo do tema.

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    const colorScheme = useColorScheme();
    const [themeType, setThemeType] = useState<ThemeType>(colorScheme || 'light');

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Criaremos uma simples função para alternar o tipo do tema.

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    // ...

    const toggleThemeType = useCallback(() => {
      setThemeType(prev => (prev === 'dark' ? 'light' : 'dark'));
    }, []);

    // ...
}
Enter fullscreen mode Exit fullscreen mode

E também definiremos isDarkTheme e o tema em si a ser utilizado

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
    // ...

    const isDarkTheme = useMemo(() => themeType === 'dark', [themeType]);
  const theme = useMemo(
    () => (isDarkTheme ? darkTheme : lightTheme),
    [isDarkTheme],
  );

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Agora que temos todos os valores do nosso context definidos, podemos renderizar o NavigationContainer e o PaperProvider para passar o tema e também nosso provider com os valores do context.

O componente completo ficaria da seguinte forma

export const ThemeContextProvider = ({children}: ThemeContextProviderProps) => {
  const colorScheme = useColorScheme();
  const [themeType, setThemeType] = useState<ThemeType>(colorScheme || 'light');

  const toggleThemeType = useCallback(() => {
    setThemeType(prev => (prev === 'dark' ? 'light' : 'dark'));
  }, []);

  const isDarkTheme = useMemo(() => themeType === 'dark', [themeType]);
  const theme = useMemo(
    () => (isDarkTheme ? darkTheme : lightTheme),
    [isDarkTheme],
  );

  return (
    <NavigationContainer theme={theme}>
      <PaperProvider theme={theme}>
        <ThemeContext.Provider
          value={{
            theme,
            themeType,
            isDarkTheme,
            setThemeType,
            toggleThemeType,
          }}>
          {children}
        </ThemeContext.Provider>
      </PaperProvider>
    </NavigationContainer>
  );
};
Enter fullscreen mode Exit fullscreen mode

Utilizando o Context e alterando o tema

Em nosso App.tsx iremos renderizar o ThemeContextProvider e dentro dele utilizaremos um stack para a navegação através do createStackNavigator. Dentro desse stack teremos uma tela apenas para demonstrar que o tema está funcionando.

const TestScreen = () => {
    // ...
};

const Stack = createStackNavigator();

const App = () => {
  return (
    <ThemeContextProvider>
      <Stack.Navigator>
        <Stack.Screen name="Test" component={TestScreen} />
      </Stack.Navigator>
    </ThemeContextProvider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Dentro da nossa tela de teste, podemos utilizar o custom hook que criamos useTheme para pegar os valores do context e utilizarmos da forma que preferirmos.

const TestScreen = () => {
  const {toggleThemeType, themeType, isDarkTheme, theme} = useTheme();

  return (
    <View>
      <Button mode="contained" onPress={toggleThemeType}>
        Toggle Theme
      </Button>
      <Headline>{themeType}</Headline>
      <Headline>isDarkTheme: {`${isDarkTheme}`}</Headline>
      <Headline>Primary: {theme.colors.primary}</Headline>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Assim, apertando o botão podemos ver que o tema muda.

Podemos analisar também que se você colocar o celular no modo escuro, o aplicativo já inicia o tema como dark.

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (1)

Collapse
 
saimwebhr profile image
Muhammad Saim Hashmi

You saved my day :) #Respect

Sentry mobile image

Improving mobile performance, from slow screens to app start time

Based on our experience working with thousands of mobile developer teams, we developed a mobile monitoring maturity curve.

Read more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay