Dark Theme com React Navigation + Typescript + React Native Paper

magoacademico

Mago Acadêmico

Posted on August 22, 2022

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.

💖 💪 🙅 🚩
magoacademico
Mago Acadêmico

Posted on August 22, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related